From 4dbec0f186d8b8d003d4befc102f043506a4f323 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 11:53:10 +0100 Subject: [PATCH 01/14] Add initial test files and configurations for test repositories --- .github/workflows/Action-Test-Src-Default.yml | 51 +++ .../Action-Test-Src-WithManifest.yml | 51 +++ .github/workflows/Action-Test-outputs.yml | 51 +++ .github/workflows/Action-Test.yml | 32 -- .github/workflows/Auto-Configure.yml | 34 -- .github/workflows/Auto-Document.yml | 31 -- .github/workflows/Auto-Release.yml | 70 ++-- About_Rules.md | 391 +++++++++++++++++ LICENSE | 42 +- action.yml | 49 ++- scripts/main.ps1 | 51 +-- .../PSScriptAnalyzer/Module.Settings.psd1 | 53 +++ .../PSScriptAnalyzer.Configuration.ps1 | 5 + .../PSScriptAnalyzer.Container.ps1 | 9 + .../PSScriptAnalyzer.Tests.ps1 | 71 ++++ .../PSScriptAnalyzer/SourceCode.Settings.psd1 | 56 +++ tests/README.md | 3 - .../docs/PSModuleTest/Get-PSModuleTest.md | 72 ++++ .../docs/PSModuleTest/New-PSModuleTest.md | 72 ++++ .../docs/PSModuleTest/Set-PSModuleTest.md | 72 ++++ .../docs/PSModuleTest/Test-PSModuleTest.md | 72 ++++ .../modules/PSModuleTest/PSModuleTest.psd1 | 75 ++++ .../modules/PSModuleTest/PSModuleTest.psm1 | 392 ++++++++++++++++++ .../PSModuleTest/assemblies/LsonLib.dll | Bin 0 -> 43520 bytes .../modules/PSModuleTest/data/Config.psd1 | 3 + .../modules/PSModuleTest/data/Settings.psd1 | 3 + .../formats/CultureInfo.Format.ps1xml | 37 ++ .../formats/Mygciview.Format.ps1xml | 65 +++ .../PSModuleTest/modules/OtherPSModule.psm1 | 19 + .../modules/PSModuleTest/scripts/loader.ps1 | 3 + .../types/DirectoryInfo.Types.ps1xml | 21 + .../PSModuleTest/types/FileInfo.Types.ps1xml | 14 + tests/srcTestRepo/README.md | 3 + tests/srcTestRepo/icon/icon.png | Bin 0 -> 5882 bytes tests/srcTestRepo/mkdocs.yml | 75 ++++ tests/srcTestRepo/src/assemblies/LsonLib.dll | Bin 0 -> 43520 bytes .../src/classes/private/SecretWriter.ps1 | 15 + tests/srcTestRepo/src/classes/public/Book.ps1 | 147 +++++++ tests/srcTestRepo/src/data/Config.psd1 | 3 + tests/srcTestRepo/src/data/Settings.psd1 | 3 + tests/srcTestRepo/src/finally.ps1 | 3 + .../src/formats/CultureInfo.Format.ps1xml | 37 ++ .../src/formats/Mygciview.Format.ps1xml | 65 +++ .../private/Get-InternalPSModule.ps1 | 18 + .../private/Set-InternalPSModule.ps1 | 22 + .../public/PSModule/Get-PSModuleTest.ps1 | 23 + .../public/PSModule/New-PSModuleTest.ps1 | 37 ++ .../src/functions/public/PSModule/PSModule.md | 1 + .../public/SomethingElse/Set-PSModuleTest.ps1 | 22 + .../public/SomethingElse/SomethingElse.md | 1 + .../functions/public/Test-PSModuleTest.ps1 | 18 + .../src/functions/public/completers.ps1 | 8 + tests/srcTestRepo/src/header.ps1 | 3 + tests/srcTestRepo/src/init/initializer.ps1 | 3 + .../src/modules/OtherPSModule.psm1 | 19 + tests/srcTestRepo/src/scripts/loader.ps1 | 3 + .../src/types/DirectoryInfo.Types.ps1xml | 21 + .../src/types/FileInfo.Types.ps1xml | 14 + .../variables/private/PrivateVariables.ps1 | 47 +++ .../src/variables/public/Moons.ps1 | 6 + .../src/variables/public/Planets.ps1 | 20 + .../src/variables/public/SolarSystems.ps1 | 17 + tests/srcTestRepo/tests/Environment.Tests.ps1 | 15 + .../srcTestRepo/tests/PSModuleTest.Tests.ps1 | 44 ++ tests/srcWithManifestTestRepo/README.md | 3 + tests/srcWithManifestTestRepo/icon/icon.png | Bin 0 -> 5882 bytes tests/srcWithManifestTestRepo/mkdocs.yml | 75 ++++ .../src/assemblies/LsonLib.dll | Bin 0 -> 43520 bytes .../src/classes/private/SecretWriter.ps1 | 15 + .../src/classes/public/Book.ps1 | 147 +++++++ .../src/data/Config.psd1 | 3 + .../src/data/Settings.psd1 | 3 + tests/srcWithManifestTestRepo/src/finally.ps1 | 3 + .../src/formats/CultureInfo.Format.ps1xml | 37 ++ .../src/formats/Mygciview.Format.ps1xml | 65 +++ .../private/Get-InternalPSModule.ps1 | 18 + .../private/Set-InternalPSModule.ps1 | 22 + .../public/PSModule/Get-PSModuleTest.ps1 | 23 + .../public/PSModule/New-PSModuleTest.ps1 | 37 ++ .../src/functions/public/PSModule/PSModule.md | 1 + .../public/SomethingElse/Set-PSModuleTest.ps1 | 22 + .../public/SomethingElse/SomethingElse.md | 1 + .../functions/public/Test-PSModuleTest.ps1 | 18 + .../src/functions/public/completers.ps1 | 8 + tests/srcWithManifestTestRepo/src/header.ps1 | 3 + .../src/init/initializer.ps1 | 3 + .../srcWithManifestTestRepo/src/manifest.psd1 | 3 + .../src/modules/OtherPSModule.psm1 | 19 + .../src/scripts/loader.ps1 | 3 + .../src/types/DirectoryInfo.Types.ps1xml | 21 + .../src/types/FileInfo.Types.ps1xml | 14 + .../variables/private/PrivateVariables.ps1 | 47 +++ .../src/variables/public/Moons.ps1 | 6 + .../src/variables/public/Planets.ps1 | 20 + .../src/variables/public/SolarSystems.ps1 | 17 + .../tests/Environments/Environment.Tests.ps1 | 15 + .../tests/MyTests/PSModuleTest.Tests.ps1 | 44 ++ .../srcWithManifestTestRepo/tools/1-build.ps1 | 1 + .../srcWithManifestTestRepo/tools/2-build.ps1 | 1 + 99 files changed, 3207 insertions(+), 194 deletions(-) create mode 100644 .github/workflows/Action-Test-Src-Default.yml create mode 100644 .github/workflows/Action-Test-Src-WithManifest.yml create mode 100644 .github/workflows/Action-Test-outputs.yml delete mode 100644 .github/workflows/Action-Test.yml delete mode 100644 .github/workflows/Auto-Configure.yml delete mode 100644 .github/workflows/Auto-Document.yml create mode 100644 About_Rules.md create mode 100644 scripts/tests/PSScriptAnalyzer/Module.Settings.psd1 create mode 100644 scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Configuration.ps1 create mode 100644 scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Container.ps1 create mode 100644 scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 create mode 100644 scripts/tests/PSScriptAnalyzer/SourceCode.Settings.psd1 delete mode 100644 tests/README.md create mode 100644 tests/outputTestRepo/outputs/docs/PSModuleTest/Get-PSModuleTest.md create mode 100644 tests/outputTestRepo/outputs/docs/PSModuleTest/New-PSModuleTest.md create mode 100644 tests/outputTestRepo/outputs/docs/PSModuleTest/Set-PSModuleTest.md create mode 100644 tests/outputTestRepo/outputs/docs/PSModuleTest/Test-PSModuleTest.md create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/PSModuleTest.psd1 create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/PSModuleTest.psm1 create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/assemblies/LsonLib.dll create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/data/Config.psd1 create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/data/Settings.psd1 create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/formats/CultureInfo.Format.ps1xml create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/formats/Mygciview.Format.ps1xml create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/modules/OtherPSModule.psm1 create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/scripts/loader.ps1 create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/types/DirectoryInfo.Types.ps1xml create mode 100644 tests/outputTestRepo/outputs/modules/PSModuleTest/types/FileInfo.Types.ps1xml create mode 100644 tests/srcTestRepo/README.md create mode 100644 tests/srcTestRepo/icon/icon.png create mode 100644 tests/srcTestRepo/mkdocs.yml create mode 100644 tests/srcTestRepo/src/assemblies/LsonLib.dll create mode 100644 tests/srcTestRepo/src/classes/private/SecretWriter.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/Book.ps1 create mode 100644 tests/srcTestRepo/src/data/Config.psd1 create mode 100644 tests/srcTestRepo/src/data/Settings.psd1 create mode 100644 tests/srcTestRepo/src/finally.ps1 create mode 100644 tests/srcTestRepo/src/formats/CultureInfo.Format.ps1xml create mode 100644 tests/srcTestRepo/src/formats/Mygciview.Format.ps1xml create mode 100644 tests/srcTestRepo/src/functions/private/Get-InternalPSModule.ps1 create mode 100644 tests/srcTestRepo/src/functions/private/Set-InternalPSModule.ps1 create mode 100644 tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 create mode 100644 tests/srcTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1 create mode 100644 tests/srcTestRepo/src/functions/public/PSModule/PSModule.md create mode 100644 tests/srcTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 create mode 100644 tests/srcTestRepo/src/functions/public/SomethingElse/SomethingElse.md create mode 100644 tests/srcTestRepo/src/functions/public/Test-PSModuleTest.ps1 create mode 100644 tests/srcTestRepo/src/functions/public/completers.ps1 create mode 100644 tests/srcTestRepo/src/header.ps1 create mode 100644 tests/srcTestRepo/src/init/initializer.ps1 create mode 100644 tests/srcTestRepo/src/modules/OtherPSModule.psm1 create mode 100644 tests/srcTestRepo/src/scripts/loader.ps1 create mode 100644 tests/srcTestRepo/src/types/DirectoryInfo.Types.ps1xml create mode 100644 tests/srcTestRepo/src/types/FileInfo.Types.ps1xml create mode 100644 tests/srcTestRepo/src/variables/private/PrivateVariables.ps1 create mode 100644 tests/srcTestRepo/src/variables/public/Moons.ps1 create mode 100644 tests/srcTestRepo/src/variables/public/Planets.ps1 create mode 100644 tests/srcTestRepo/src/variables/public/SolarSystems.ps1 create mode 100644 tests/srcTestRepo/tests/Environment.Tests.ps1 create mode 100644 tests/srcTestRepo/tests/PSModuleTest.Tests.ps1 create mode 100644 tests/srcWithManifestTestRepo/README.md create mode 100644 tests/srcWithManifestTestRepo/icon/icon.png create mode 100644 tests/srcWithManifestTestRepo/mkdocs.yml create mode 100644 tests/srcWithManifestTestRepo/src/assemblies/LsonLib.dll create mode 100644 tests/srcWithManifestTestRepo/src/classes/private/SecretWriter.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/Book.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/data/Config.psd1 create mode 100644 tests/srcWithManifestTestRepo/src/data/Settings.psd1 create mode 100644 tests/srcWithManifestTestRepo/src/finally.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/formats/CultureInfo.Format.ps1xml create mode 100644 tests/srcWithManifestTestRepo/src/formats/Mygciview.Format.ps1xml create mode 100644 tests/srcWithManifestTestRepo/src/functions/private/Get-InternalPSModule.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/functions/private/Set-InternalPSModule.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/functions/public/PSModule/PSModule.md create mode 100644 tests/srcWithManifestTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/functions/public/SomethingElse/SomethingElse.md create mode 100644 tests/srcWithManifestTestRepo/src/functions/public/Test-PSModuleTest.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/functions/public/completers.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/header.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/init/initializer.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/manifest.psd1 create mode 100644 tests/srcWithManifestTestRepo/src/modules/OtherPSModule.psm1 create mode 100644 tests/srcWithManifestTestRepo/src/scripts/loader.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/types/DirectoryInfo.Types.ps1xml create mode 100644 tests/srcWithManifestTestRepo/src/types/FileInfo.Types.ps1xml create mode 100644 tests/srcWithManifestTestRepo/src/variables/private/PrivateVariables.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/variables/public/Moons.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/variables/public/Planets.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/variables/public/SolarSystems.ps1 create mode 100644 tests/srcWithManifestTestRepo/tests/Environments/Environment.Tests.ps1 create mode 100644 tests/srcWithManifestTestRepo/tests/MyTests/PSModuleTest.Tests.ps1 create mode 100644 tests/srcWithManifestTestRepo/tools/1-build.ps1 create mode 100644 tests/srcWithManifestTestRepo/tools/2-build.ps1 diff --git a/.github/workflows/Action-Test-Src-Default.yml b/.github/workflows/Action-Test-Src-Default.yml new file mode 100644 index 0000000..c48f749 --- /dev/null +++ b/.github/workflows/Action-Test-Src-Default.yml @@ -0,0 +1,51 @@ +name: Action-Test [Src-Default] + +run-name: "Action-Test [Src-Default] - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" + +on: + workflow_dispatch: + pull_request: + schedule: + - cron: '0 0 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + ActionTest: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + name: Action-Test [Src-Default] - [${{ matrix.os }}] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Initialize environment + uses: PSModule/Initialize-PSModule@main + + - name: Action-Test + uses: ./ + id: action-test + env: + GITHUB_TOKEN: ${{ github.token }} + with: + Name: PSModuleTest + Path: tests/srcTestRepo + TestType: SourceCode + OS: ${{ matrix.os }} + + - name: Status + shell: pwsh + env: + PASSED: ${{ steps.action-test.outputs.passed }} + run: | + Write-Host "Passed: [$env:PASSED]" + if ($env:PASSED -ne 'true') { + exit 1 + } diff --git a/.github/workflows/Action-Test-Src-WithManifest.yml b/.github/workflows/Action-Test-Src-WithManifest.yml new file mode 100644 index 0000000..3ac93cc --- /dev/null +++ b/.github/workflows/Action-Test-Src-WithManifest.yml @@ -0,0 +1,51 @@ +name: Action-Test [Src-WithManifest] + +run-name: "Action-Test [Src-WithManifest] - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" + +on: + workflow_dispatch: + pull_request: + schedule: + - cron: '0 0 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + ActionTest: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + name: Action-Test [Src-WithManifest] - [${{ matrix.os }}] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Initialize environment + uses: PSModule/Initialize-PSModule@main + + - name: Action-Test + uses: ./ + id: action-test + env: + GITHUB_TOKEN: ${{ github.token }} + with: + Name: PSModuleTest + Path: tests/srcWithManifestTestRepo + Test: SourceCode + OS: ${{ matrix.os }} + + - name: Status + shell: pwsh + env: + PASSED: ${{ steps.action-test.outputs.passed }} + run: | + Write-Host "Passed: [$env:PASSED]" + if ($env:PASSED -ne 'true') { + exit 1 + } diff --git a/.github/workflows/Action-Test-outputs.yml b/.github/workflows/Action-Test-outputs.yml new file mode 100644 index 0000000..0406a4c --- /dev/null +++ b/.github/workflows/Action-Test-outputs.yml @@ -0,0 +1,51 @@ +name: Action-Test [outputs] + +run-name: "Action-Test [outputs] - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" + +on: + workflow_dispatch: + pull_request: + schedule: + - cron: '0 0 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + ActionTest: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + name: Action-Test [outputs] - [${{ matrix.os }}] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Initialize environment + uses: PSModule/Initialize-PSModule@main + + - name: Action-Test + uses: ./ + id: action-test + env: + GITHUB_TOKEN: ${{ github.token }} + with: + Name: PSModuleTest + Path: tests/outputTestRepo + TestType: Module + OS: ${{ matrix.os }} + + - name: Status + shell: pwsh + env: + PASSED: ${{ steps.action-test.outputs.passed }} + run: | + Write-Host "Passed: [$env:PASSED]" + if ($env:PASSED -ne 'true') { + exit 1 + } diff --git a/.github/workflows/Action-Test.yml b/.github/workflows/Action-Test.yml deleted file mode 100644 index 6ff2f13..0000000 --- a/.github/workflows/Action-Test.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Action-Test - -run-name: "Action-Test - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" - -on: - workflow_dispatch: - pull_request: - schedule: - - cron: '0 0 * * *' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - pull-requests: read - -jobs: - ActionTestBasic: - name: Action-Test - [Basic] - runs-on: ubuntu-latest - steps: - # Need to check out as part of the test, as its a local action - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Action-Test - uses: ./ - with: - working-directory: ./tests - subject: PSModule diff --git a/.github/workflows/Auto-Configure.yml b/.github/workflows/Auto-Configure.yml deleted file mode 100644 index e2321e4..0000000 --- a/.github/workflows/Auto-Configure.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Auto-Configure - -run-name: "Auto-Configure - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" - -on: - pull_request_target: - branches: - - main - types: - - closed - - opened - - reopened - - synchronize - - labeled - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: write # Required to create releases - pull-requests: write # Required to create comments on the PRs - -jobs: - Auto-Configure: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Auto-Configure - uses: PSModule/Auto-Configure@v1 - env: - GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/Auto-Document.yml b/.github/workflows/Auto-Document.yml deleted file mode 100644 index 6a62053..0000000 --- a/.github/workflows/Auto-Document.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Auto-Document - -run-name: "Auto-Document - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" - -on: - pull_request_target: - branches: - - main - types: - - opened - - reopened - - synchronize - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: write # Required to push to the branch - -jobs: - Auto-Document: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Auto-Document - uses: PSModule/Auto-Document@v1 - env: - GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/Auto-Release.yml b/.github/workflows/Auto-Release.yml index d6c477b..ec157c9 100644 --- a/.github/workflows/Auto-Release.yml +++ b/.github/workflows/Auto-Release.yml @@ -1,34 +1,36 @@ -name: Auto-Release - -run-name: "Auto-Release - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" - -on: - pull_request_target: - branches: - - main - types: - - closed - - opened - - reopened - - synchronize - - labeled - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: write # Required to create releases - pull-requests: write # Required to create comments on the PRs - -jobs: - Auto-Release: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Auto-Release - uses: PSModule/Auto-Release@v1 - env: - GITHUB_TOKEN: ${{ github.token }} +name: Auto-Release + +run-name: "Auto-Release - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" + +on: + pull_request_target: + branches: + - main + types: + - closed + - opened + - reopened + - synchronize + - labeled + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: write + +jobs: + Auto-Release: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Auto-Release + uses: PSModule/Auto-Release@v1 + env: + GITHUB_TOKEN: ${{ github.token }} # Used for GitHub CLI authentication + with: + IncrementalPrerelease: false diff --git a/About_Rules.md b/About_Rules.md new file mode 100644 index 0000000..b66ca58 --- /dev/null +++ b/About_Rules.md @@ -0,0 +1,391 @@ +# Using a Hashtable-Based Settings File for PSScriptAnalyzer in PowerShell 7 + +## Introduction + +**PSScriptAnalyzer** is a static code checker for PowerShell modules and scripts. It evaluates your code against a set of built-in rules based on PowerShell best practices identified by the PowerShell team and community ([PSScriptAnalyzer/README.md at main · PowerShell/PSScriptAnalyzer · GitHub](https://github.com/PowerShell/PSScriptAnalyzer/blob/master/README.md#:~:text=PSScriptAnalyzer%20is%20a%20static%20code,suggests%20possible%20solutions%20for%20improvements)). When run (for example, via the `Invoke-ScriptAnalyzer` cmdlet), it produces diagnostics (errors, warnings, or informational messages) to highlight potential issues and suggest improvements. This helps maintain code quality by catching common mistakes, stylistic issues, or potential bugs early in the development process. + +Using a **settings file** for PSScriptAnalyzer allows you to customize which rules are applied and how they're reported, without having to specify numerous parameters on each run. In effect, a settings file acts as a profile or configuration that PSScriptAnalyzer will follow, much like *splatting* parameters in a single hashtable ([Invoke-ScriptAnalyzer (PSScriptAnalyzer) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules#:~:text=The%20keys%20and%20values%20in,For%20more%20information%2C%20see%20about_Splatting)). This approach is beneficial for several reasons: + +- **Consistency**: By sharing a common settings file across your project or team, you ensure that everyone’s code is analyzed with the same rules and standards. This avoids “it works on my machine” discrepancies in linting results. +- **Customization**: Not all projects are the same. A settings file allows you to **include or exclude specific rules** and even filter by severity levels to tailor the analysis to your project's needs ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=The%20following%20example%20excludes%20two,other%20than%20Error%20and%20Warning)). For instance, you might choose to treat only errors and warnings as relevant, ignoring info-level suggestions in certain CI scenarios. +- **Maintainability**: It's easier to update one configuration file than to modify multiple build or script invocations. If you decide to enable a new rule or adjust severities, you can do it in one place. The settings file “does everything the different parameters on `Invoke-ScriptAnalyzer` [would do]” ([Creating custom PSScriptAnalyzer rules](https://blog.ironmansoftware.com/psscriptanalyzer-custom-rules/#:~:text=In%20order%20to%20customize%20,ScriptAnalyzer)), so it centralizes your static analysis configuration. +- **Integration**: Many tools (like VS Code, CI/CD pipelines, and GitHub actions) can automatically pick up your PSScriptAnalyzer settings file. This implicit usage means you often just drop the file in the right location and your rules preferences will be applied without extra scripting ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). + +In summary, a hashtable-based settings file gives you fine-grained control over PSScriptAnalyzer’s behavior. In the latest PowerShell (Core 7+) environment, PSScriptAnalyzer fully supports such configuration files, making it easier to enforce coding standards and best practices consistently across various contexts. + +## Basic Setup + +This section guides you through creating a PSScriptAnalyzer settings file (which is a PowerShell **.psd1 data file**) and getting it ready for use. We will assume a common scenario of using a GitHub repository, with the settings file stored at `.github/linters/.powershell-psscriptanalyzer.psd1` (a location conventionally used by some CI linters), but you can adapt the location to your needs. The file itself can be named anything, but by default PSScriptAnalyzer looks for **`PSScriptAnalyzerSettings.psd1`** if no explicit path is provided ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). + +**Steps to create the settings file:** + +1. **Choose a Location:** Decide where to place the settings file. For automatic discovery, the recommended name is `PSScriptAnalyzerSettings.psd1` in the root of your project (so that if you run PSScriptAnalyzer on the project folder, it finds the file) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). If you are using GitHub's Super-Linter Action or similar, the convention is to put the file under a `.github/linters/` directory with a language-specific name. For example, GitHub Super-Linter will look for a PowerShell analyzer config at `.github/linters/.powershell-psscriptanalyzer.psd1` by default ([ + PowerShell Gallery + | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview + ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=Documentation%3A%20https%3A%2F%2Fgithub.com%2FPowerShell%2FPSScriptAnalyzer%2Fblob%2Fmaster%2Fdocs%2Fmarkdown%2FInvoke,RecurseCustomRulePath%3D%27path%5Cof%5Ccustomrules%27%20Severity%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3D)). Create the directories if they don't exist: + ```bash + mkdir -p .github/linters + ``` + Then create an empty file named `.powershell-psscriptanalyzer.psd1` in that folder. + +2. **File Structure:** Open the file in a text editor. A PSScriptAnalyzer settings file is essentially a PowerShell hashtable literal enclosed in `@{ ... }`. Start with an empty hashtable: + ```powershell + @{} + ``` + This empty config would mean "use all default rules with default severities." It’s a valid starting point, but typically you will add keys and values to customize the analysis. Each key in this hashtable corresponds to a PSScriptAnalyzer parameter or setting (like which rules to include/exclude). We will cover these keys in the next sections. + +3. **Add Basic Configuration:** As a quick test, you might add a simple setting. For example, to only show errors (and hide warnings/information messages), you could set the **Severity** filter in the file: + ```powershell + @{ + Severity = @('Error') + } + ``` + This is a minimal configuration that tells PSScriptAnalyzer to report only rule violations of severity "Error" ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=%40%7B%20,Error%27%29)). For now, you can save the file with just this content or another simple tweak. We will expand on the various settings in subsequent sections. + +4. **Using the Settings File:** To verify that your settings file is recognized, run PSScriptAnalyzer with it. From a PowerShell prompt at the root of your project, execute: + ```powershell + Invoke-ScriptAnalyzer -Path . -Recurse -Settings .\.github\linters\.powershell-psscriptanalyzer.psd1 + ``` + Replace the `-Path` with the path to your scripts (here we use `.` for current directory, and `-Recurse` to analyze all subfolders). The `-Settings` parameter points to the file we created. PSScriptAnalyzer will load the hashtable from that file and apply those settings for the analysis run. You should see that the output now reflects your configuration (e.g., only errors if you set `Severity='Error'` earlier). If you placed a file named `PSScriptAnalyzerSettings.psd1` at the root of the path you're analyzing, you could even omit the `-Settings` parameter and PSScriptAnalyzer would **implicitly** find it ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). + +5. **Commit the File (if applicable):** If this is for a repository (especially with CI integration), be sure to add and commit the new `.psd1` file to version control. This allows the settings to travel with the code, so that other developers and automated pipelines use the same analyzer configuration. + +At this point, you have a basic settings file set up. Next, we'll dive into how to configure specific rules and options within that file to fine-tune the analyzer to your needs. + +## Configuring Rules + +The power of a PSScriptAnalyzer settings file comes from the ability to **enable or disable specific rules** and **adjust filtering options like severity** in one place. The settings file uses certain predefined keys (entries in the hashtable) that PSScriptAnalyzer recognizes. These keys correspond to parameters you might otherwise pass to `Invoke-ScriptAnalyzer`. The most commonly used keys include: + +- **`IncludeRules`** – A list of rule names to **include** (run) during analysis ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=,)). Only rules in this list will be executed (all others will be skipped). Wildcards are supported (e.g., `'PSAvoid*'` to include all rules starting with "PSAvoid"). +- **`ExcludeRules`** – A list of rule names to **exclude** from analysis ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=,)). All rules except those listed will run. Use this when you want most rules enabled, but need to turn off a few that aren't relevant or cause noise. Wildcards can be used here as well. +- **`Severity`** – A list of severity levels to report. By default, PSScriptAnalyzer rules can output findings of severity **Error**, **Warning**, or **Information**. Using this setting filters out findings that are not in the list. For example, `Severity = @('Error','Warning')` means informational messages will be omitted from results ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Severity%3D%40%28%27Error%27%2C%27Warning%27%29%20ExcludeRules%3D%40%28%27PSAvoidUsingCmdletAliases%27%2C%20%27PSAvoidUsingWriteHost%27%29%20)). This does **not** change the inherent severity of rules; it just acts as a post-analysis filter for what gets reported ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=The%20following%20example%20excludes%20two,other%20than%20Error%20and%20Warning)). (Note: Currently, you cannot directly change a rule's designated severity via the settings file – you can only filter what severities to see.) +- **`IncludeDefaultRules`** – A Boolean switch ( `$true` / `$false` ) that determines whether the built-in default rules should be included. This is typically used in combination with custom rules (discussed later). For instance, if you are using custom rules and want to **also** run the normal PSScriptAnalyzer rules, set `IncludeDefaultRules = $true` ([ + PowerShell Gallery + | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview + ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=Severity%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3D%C2%A0%40%28%20%27Error%27%20%27Warning%27%20%29%20IncludeDefaultRules%C2%A0%3D%C2%A0%24,%27PSUseShouldProcessForStateChangingFunctions%27%2C%20%27PSAvoidUsingConvertToSecureStringWithPlainText)). If false (or not set, which defaults to false when custom rules are specified), you might be running only a custom rule set. In normal use (without custom rules), you don't need to set this – by default all built-in rules run unless you exclude or limit them. +- **`CustomRulePath`** – One or more filesystem paths to custom rule scripts or modules. Custom rules allow you to extend PSScriptAnalyzer with your own rules; by specifying their path here, PSSA will load and include them in analysis ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=In%20this%20example%20the%20property,used%20for%20the%20property%20IncludeRules)). (More on custom rules in its own section below.) +- **`RecurseCustomRulePath`** – A Boolean indicating if PSScriptAnalyzer should search subdirectories of the provided custom rule path(s) for additional rule files ([ + PowerShell Gallery + | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview + ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=psscriptanalyzer.psd1%20%40%7B%20,true)). Set this to `$true` if you've organized custom rules in a folder hierarchy and want all of them picked up. If you enable this, and you *only* want custom rules, remember to disable default rules (or don't set IncludeDefaultRules) to avoid running everything. +- **`Rules`** – A nested hashtable for providing **per-rule specific settings**. This is used to pass **rule parameters** to particular rules, or to override certain rule behaviors. For example, some rules have optional parameters (like a whitelist of allowed terms or specific options). If you want to provide those, you do so under `Rules`. The key is the rule name, and the value is another hashtable of that rule's parameter names and values. For example: + ```powershell + Rules = @{ + PSAvoidUsingCmdletAliases = @{ Whitelist = @('cd') } + } + ``` + This would configure the rule **PSAvoidUsingCmdletAliases** to ignore the alias `cd` (so using `cd` won't trigger a warning in that rule) ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,Whitelist%20%3D%20%40%28%27cd)). Not all rules have configurable parameters, but for those that do (like specifying compatible PowerShell versions in compatibility rules, etc.), the `Rules` key is how you pass those in. We’ll see more examples of this in **Advanced Use Cases**. + +**Enabling or Disabling Rules:** To selectively run rules, use **IncludeRules** or **ExcludeRules**. It’s generally recommended to use one of these approaches, not both, to avoid confusion – but if you do use both, note that any rule present in both lists will be *excluded* (ExcludeRules takes precedence) ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,ExcludeRules%20%3D%20%40%28%27PSAvoidUsingWriteHost%27%2C%27PSMissingModuleManifestField)). In practice: +- If you want to run a small *subset* of all rules (for example, only security-related rules), use **IncludeRules** to list them. Everything not listed is ignored. +- If you want to run most rules, but skip a few that don't apply, use **ExcludeRules** for those few, and let all others run. + +For example, your settings file might include: +```powershell +@{ + ExcludeRules = @('PSAvoidUsingWriteHost', 'PSUseWriteOutput') +} +``` +This would disable the **PSAvoidUsingWriteHost** and **PSUseWriteOutput** rules (perhaps you decide using Write-Host is acceptable in your project), and run all other default rules normally. Conversely, using IncludeRules: +```powershell +@{ + IncludeRules = @('PSAvoidHardcodingCredentials', 'PSUseApprovedVerbs') +} +``` +would run *only* those two rules (one that checks for hardcoded credentials and one that checks cmdlet naming) and no others ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=,)). + +**Adjusting Rule Severity Reporting:** Every PSScriptAnalyzer rule is defined with a severity level (information, warning, or error). These indicate how critical a finding is (with "Error" being most severe). Out of the box, most PSScriptAnalyzer rules are classified as warnings or information; truly critical issues (like syntax errors) might appear as errors. In the settings file, you can’t change a rule’s intrinsic severity, but you *can* control what severities are considered worth reporting: +- To **limit output to certain severities**, list them under the **Severity** key. For example: `Severity = @('Error','Warning')` will suppress informational messages ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Severity%3D%40%28%27Error%27%2C%27Warning%27%29%20ExcludeRules%3D%40%28%27PSAvoidUsingCmdletAliases%27%2C%20%27PSAvoidUsingWriteHost%27%29%20)). This is useful in CI/CD when you want to reduce noise (e.g., treat the build as passed even if only informational/style issues are present). In a development environment, you might use the full set including Information to get more suggestions, but in automation, you might filter out low-severity items. +- If you want to be extra strict, you could even *narrow* to `'Error'` only (meaning the build will only fail or report if an actual error-level issue is found). Or conversely, include `'Information'` if you want absolutely everything reported. +- Keep in mind that filtering by Severity happens **after** rules run ([Invoke-ScriptAnalyzer (PSScriptAnalyzer) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules#:~:text=)) ([Invoke-ScriptAnalyzer (PSScriptAnalyzer) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules#:~:text=You%20can%20specify%20one%20ore,more%20severity%20values)). PSScriptAnalyzer will still execute the rules; it just won't output the ones that don’t match the filter. If performance or efficiency is a concern (to not even run some rules), you should instead use Include/ExcludeRules to prevent running those rules at all. For instance, if you only care about error-level rules, you might identify which rules can produce warnings and exclude them instead of relying solely on the Severity filter. + +In summary, **configuring rules** via the settings file involves deciding which rules to run, which to skip, and what levels of findings to show. The table below summarizes the key configuration options and their effects: + +| **Key** | **Purpose** | **Example** | +|--------------------|------------------------------------------------------------|-----------------------------------------------------| +| `IncludeRules` | Run *only* these specific rules (names or wildcards). | `IncludeRules = @('PSUseApprovedVerbs', 'PSAvoid*')` | +| `ExcludeRules` | Run all except these specific rules. | `ExcludeRules = @('PSAvoidUsingWriteHost')` | +| `Severity` | Only report findings of these severities. | `Severity = @('Error', 'Warning')` | +| `IncludeDefaultRules` | When using custom rules, whether to also include built-in rules. | `IncludeDefaultRules = $true` (include both custom + default) | +| `CustomRulePath` | Path(s) to custom rule modules or scripts to load. | `CustomRulePath = 'path\to\MyRules.psm1'` | +| `RecurseCustomRulePath` | If true, search subfolders in CustomRulePath for rules. | `RecurseCustomRulePath = $true` | +| `Rules` | Rule-specific settings (hashtable of rule names to settings). | `Rules = @{ PSWhateverRule = @{ Param = 'Value' } }` | + +With these tools, you can fine-tune the analysis exactly as required. Next, we'll focus specifically on excluding rules and then on creating custom rules. + +## Rule Exclusions + +Sometimes you may want to **suppress certain rules** from running because they are not applicable to your scenario or perhaps yield too many false positives. Using the settings file to exclude rules is often preferable to peppering your code with suppression attributes, as it provides a single point of control. Here’s how to manage rule exclusions effectively: + +- **ExcludeRules Key:** As mentioned, this key takes an array of rule names to disable. The rule names correspond to the PSScriptAnalyzer rules (for example: PSAvoidUsingCmdletAliases, PSUseDeclaredVarsMoreThanAssignments, etc.). You can find the list of rule names via the `Get-ScriptAnalyzerRule` cmdlet or in [PSScriptAnalyzer’s rule documentation](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview#available-rules). In the settings file, list the rules to skip like so: + ```powershell + @{ + ExcludeRules = @('RuleName1', 'RuleName2', 'RuleName3') + } + ``` + For instance, to exclude the rules that discourage using Write-Host and ConvertTo-SecureString with plaintext, you could configure: + ```powershell + @{ + ExcludeRules = @('PSUseShouldProcessForStateChangingFunctions', + 'PSAvoidUsingConvertToSecureStringWithPlainText') + } + ``` + This example matches the Super-Linter default, which excludes two specific rules from the analysis ([ + PowerShell Gallery + | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview + ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=IncludeDefaultRules%C2%A0%3D%C2%A0%24,%27MyCustomRuleName%27)). The rest of the rules would still run (unless further limited by includes or severities). + +- **Wildcards:** You can use wildcard patterns to exclude groups of rules. For example, `ExcludeRules = @('PSAvoid*')` would exclude all rules whose names start with "PSAvoid". Use this with caution, as you might accidentally skip important rules. + +- **Precedence with IncludeRules:** If you happen to use both IncludeRules and ExcludeRules, note that an exclude will override an include. *“If a rule is in both IncludeRules and ExcludeRules, the rule will be excluded.”* ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,ExcludeRules%20%3D%20%40%28%27PSAvoidUsingWriteHost%27%2C%27PSMissingModuleManifestField)). This is logical – you explicitly told the analyzer to exclude it, so it won’t run even if it was also on your include list. In practice, try to stick to one approach (include-only or exclude-only) to keep the configuration clear. + +- **Temporary vs Permanent Exclusions:** If you find yourself excluding many rules, double-check if you truly need them all off. It's often better to exclude only what you must. Some teams use exclusions temporarily and aim to fix the underlying code issues so they can remove the exclusion later (for example, turning off a deprecated alias rule until they have time to refactor all uses of those aliases). + +- **Suppressing in Code vs. Settings:** PSScriptAnalyzer also supports suppressing a rule for a specific portion of code using annotations (the `[Diagnostics.CodeAnalysis.SuppressMessage()]` attribute in your script). Use that approach when a rule is generally useful, but a *specific instance* in code should be exempt. However, if you find you are suppressing the same rule in many places, it's a sign that maybe that rule should be globally excluded via the settings file (or that the team has consciously decided to not follow that particular guideline). + +In summary, **rule exclusions** in the settings file let you turn off unwanted rules globally for the analysis run. Keep the list of exclusions as short as possible to get maximum value from PSScriptAnalyzer, but do use it to disable rules that don’t make sense for your project. This ensures the output focuses only on relevant issues. + +## Custom Rules + +While PSScriptAnalyzer comes with a rich set of built-in rules, you may have organization-specific or project-specific guidelines that aren’t covered by the defaults. **Custom rules** allow you to extend PSScriptAnalyzer by writing your own rule logic. The settings file plays a crucial role in **registering these custom rules** so that PSScriptAnalyzer knows about them. + +**Creating a Custom Rule:** Custom rules are implemented as functions in a PowerShell module (.psm1) or script file. Key points for writing a custom rule module: + +- Each custom rule is a function that typically uses a verb like *Measure* or *Test* (for example, `Measure-MyCustomRule`). There's no strict naming requirement, but following a consistent verb-noun naming helps. In Microsoft’s examples, they use `Measure-` for custom rules ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=In%20this%20example%20the%20property,used%20for%20the%20property%20IncludeRules)). +- The function must accept either a `[ScriptBlockAst]` (AST of the script) or `[Token[]]` (array of tokens) as a parameter. Most custom rules work with the AST, as it provides a structured representation of the code. +- The function should return one or more **DiagnosticRecord** objects (or nothing if no issues found). A DiagnosticRecord is what PSScriptAnalyzer uses to represent a rule violation (with properties like Severity, ScriptName, Line, Message, etc.). +- Include comment-based help in your function with at least a `.Synopsis` (this becomes the rule's description) and `.Description` if needed. Also use the `[OutputType()]` attribute to declare that it outputs `DiagnosticRecord` objects ([Creating custom PSScriptAnalyzer rules](https://blog.ironmansoftware.com/psscriptanalyzer-custom-rules/#:~:text=,function%20that%20should%20be%20noted)). +- After defining the function(s) in the .psm1, make sure to **export** them (e.g., using `Export-ModuleMember -Function MyFunctionName`) ([Creating custom PSScriptAnalyzer rules](https://blog.ironmansoftware.com/psscriptanalyzer-custom-rules/#:~:text=Finally%2C%20we%20make%20sure%20to,the%20function%20from%20our%20module)). If you have multiple rules in one module, export each function that implements a rule. + +For example, imagine we want a custom rule to ensure no TODO comments are left in the code. We could create `MyCompany.AnalyzerRules.psm1` with a function `Measure-TodoComment` that scans the AST for comment tokens containing "TODO" and emits a DiagnosticRecord for each occurrence. Once that function is ready and exported from the module, we're set to wire it into PSScriptAnalyzer. + +**Using Custom Rules via Settings File:** The settings file needs to tell PSScriptAnalyzer where to find your custom rules and which ones to run. This is done with two keys we mentioned: `CustomRulePath` and (optionally) `IncludeRules`/`IncludeDefaultRules`. + +- **CustomRulePath:** Add this key with the path(s) to your custom rule module or script. For example: + ```powershell + @{ + CustomRulePath = @( + '.\Modules\MyCompany.AnalyzerRules\MyCompany.AnalyzerRules.psm1' + ) + } + ``` + You can specify multiple paths (e.g., if you have several custom rule modules) by using an array as shown. Relative paths are typically resolved relative to where you run PSScriptAnalyzer (e.g., your project root). Ensure the path is correct; if pointing to a module folder, you can just give the folder path (and it will load the module manifest if present). If pointing to a .psm1 file, include the full filename ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=In%20this%20example%20the%20property,used%20for%20the%20property%20IncludeRules)). + +- **IncludeRules for custom rules:** By default, when you provide custom rules, PSScriptAnalyzer might run *only* those custom rules (depending on how you configure IncludeDefaultRules). If you want to run all your custom rules, one easy way is to name them with a common prefix or verb and use a wildcard include. For instance, if all your custom rule functions start with `Measure-`, you could do: + ```powershell + @{ + CustomRulePath = '.\AnalyzerRules\CustomRules.psm1' + IncludeRules = @('Measure-*') + } + ``` + This tells PSSA to include any rules whose names match "Measure-*", which should rope in all functions from your custom module (assuming they use that naming convention) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=In%20this%20example%20the%20property,used%20for%20the%20property%20IncludeRules)) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=IncludeRules%20%20%20%20,%29)). + +- **Including default rules as well:** If you want to *add* custom rules on top of the standard ones (common case – you usually want both your rules and the built-ins), you should set `IncludeDefaultRules = $true` in the hashtable ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=IncludeDefaultRules%20%3D%20%24true)). By doing so, you ensure that the default rule set remains active alongside your custom ones. Then you have two options: + 1. **Run all default rules + all custom rules:** In this case, you might not even need an IncludeRules list; simply adding a CustomRulePath and setting IncludeDefaultRules `$true` might load everything (all defaults and all functions found in custom path). However, to be explicit, you could list some or all rules in `IncludeRules`. + 2. **Run a selection of default rules + specific custom rules:** List exactly which rules to run in `IncludeRules` (both default and custom). For example: + ```powershell + @{ + CustomRulePath = @('.\MyRules\MyRules.psm1') + IncludeDefaultRules = $true + IncludeRules = @( + # select some default rules + 'PSAvoidDefaultValueForMandatoryParameter', + 'PSUseApprovedVerbs', + # select custom rules by name + 'Measure-TodoComment', + 'Measure-SecretInScript' + ) + } + ``` + In this snippet, we point to a custom rules module, allow default rules, and explicitly include two specific default rules and two custom rules by name. Only those four rules would run. + +A real example from Microsoft’s documentation shows including both default and custom rules by mixing them in the IncludeRules list and enabling default rules: +```powershell +@{ + CustomRulePath = @( + '.\output\RequiredModules\DscResource.AnalyzerRules', + '.\tests\QA\AnalyzerRules\SqlServerDsc.AnalyzerRules.psm1' + ) + IncludeDefaultRules = $true + IncludeRules = @( + # Default rules + 'PSAvoidDefaultValueForMandatoryParameter', + 'PSAvoidDefaultValueSwitchParameter', + # Custom rules + 'Measure-*' + ) +} +``` +In this case, any rule in the custom modules matching `Measure-*` will run, plus the two named default rules (and no other default rules) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=%40,SqlServerDsc.AnalyzerRules.psm1%27)) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=%27PSAvoidDefaultValueForMandatoryParameter%27%20%27PSAvoidDefaultValueSwitchParameter%27)). + +**Using Custom Rules in VS Code:** If you're working in Visual Studio Code with the PowerShell extension, you can have it use your settings (and thus your custom rules) by pointing to the settings file in your workspace settings. In your `.vscode/settings.json`, set: +```json +{ + "powershell.scriptAnalysis.settingsPath": ".github/linters/.powershell-psscriptanalyzer.psd1", + "powershell.scriptAnalysis.enable": true +} +``` +This ensures that the editor, when providing real-time PSScriptAnalyzer feedback, uses your settings (including loading any custom rule modules) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=workspace%20settings%20file%20%28)). Without this, VS Code might only use default rules or its own default settings. + +**Testing Custom Rules:** After configuring, run `Invoke-ScriptAnalyzer` on a sample script that should trigger your custom rule to verify it's working. If it doesn't appear to run: +- Check that the path in CustomRulePath is correct. Try an absolute path to be sure. +- Ensure the module is not throwing errors on import (e.g., try an `Import-Module` on it). +- Confirm the function name or pattern is correctly listed in IncludeRules (if you use that). +- Make sure `IncludeDefaultRules` is set appropriately. If your custom rule names don't match any default rules and you **only** want custom rules, you might set `IncludeDefaultRules = $false` (or omit it, as false is default when IncludeRules is specified) to avoid running built-ins. +- If still not working, run PSScriptAnalyzer in verbose mode or check for any warnings about loading rules. There's also a built-in rule "PSScriptAnalyzerSettingsSchema" (available in newer versions) that can validate your settings file structure to catch mistakes ([[Resolved] PSSCriptAnalyzer warnings in VSCode](https://forums.ironmansoftware.com/t/resolved-psscriptanalyzer-warnings-in-vscode/3602#:~:text=rule%20definitions%20can%20be%20read,com%20%C2%B7%20PowerShell)). + +Custom rules empower you to enforce project-specific guidelines. Once set up in the settings file, they integrate seamlessly – from the command line, to editors, to CI pipelines – just like the built-in PSScriptAnalyzer rules. + +## Advanced Use Cases + +In this section, we explore advanced scenarios and fine-tuning techniques for PSScriptAnalyzer settings. These go beyond the basic include/exclude and custom rules to address project-specific needs and edge cases. + +### Fine-Tuning for Specific Projects + +Every project might have its own quirks. The settings file can be adjusted to handle those: + +- **Different Settings per Project**: If you maintain multiple projects (or modules) in one repository that have distinct guidelines, you can create multiple settings files. For example, if you have a module in one folder that requires strict rules and a script in another that requires a looser rule set, you might use `Invoke-ScriptAnalyzer -Path ModuleA -Settings .\ModuleA\PSScriptAnalyzerSettings.psd1` for one and a different settings file for the other. You could even automate this via separate CI jobs or scripts for each sub-project. Keep each settings file alongside its project files for clarity. +- **Built-in Presets**: PSScriptAnalyzer offers some built-in presets which are essentially pre-configured rule sets (invoked by passing a special value to the `-Settings` parameter, like "PSGallery", "DSC", or "CodeFormatting") ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Built)). While these aren't hashtable files you edit, it's good to know about them. For instance, the "PSGallery" preset focuses on rules relevant to publishing modules to PowerShell Gallery (e.g., required metadata), and "DSC" preset focuses on Desired State Configuration script guidelines. Advanced users sometimes start with a preset and then modify further via a custom settings file if needed. +- **Rule Parameter Customization**: We introduced the `Rules` hashtable for passing rule-specific settings. This is particularly useful for **edge cases** like: + - Whitelisting or blacklisting certain elements. e.g., *PSAvoidUsingCmdletAliases* rule normally warns on any alias usage. If your project is okay with a few specific aliases (like `ls` or `gc`), add them to a whitelist: + ```powershell + Rules = @{ PSAvoidUsingCmdletAliases = @{ Whitelist = @('ls','gc') } } + ``` + Now those aliases won't trigger the rule ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,Whitelist%20%3D%20%40%28%27cd)). + - Configuring compatibility checks. PSScriptAnalyzer has rules like **PSUseCompatibleCmdlets** which can check if your script's cmdlets exist in other PowerShell versions. That rule takes a `Compatibility` parameter (specifying target platforms/versions). You can set in the settings file: + ```powershell + Rules = @{ PSUseCompatibleCmdlets = @{ Compatibility = @('WindowsPowerShell_5.1', 'PowerShellCore_7.0') } } + ``` + This would make the rule check compatibility against Windows PowerShell 5.1 and PowerShell 7.0. Without such specification, it might default to some baseline or require manual parameter each time. + - Adjusting formatting rules. If you use the PSScriptAnalyzer formatting features (via `Invoke-Formatter` or the VSCode formatting), you might have formatting rules that accept settings (indentation style, etc.). Those too can often be configured via the settings file under the `Rules` key for the specific formatting rule. + +- **Excluding Files or Paths**: Unlike some linters, PSScriptAnalyzer’s settings file does not have a direct way to exclude specific file paths from analysis. File scoping is usually handled when invoking the tool (e.g., you choose what `-Path` to analyze, or you could script to skip certain files). However, you can simulate per-path rule exclusions by combining with the ability to suppress in-code or running separate passes. One advanced approach is to run PSScriptAnalyzer multiple times on different sets of files with different settings (perhaps via a script). For example, you might run it on all files normally, but on a specific problematic script with a special settings file that excludes a rule that doesn't play well with that script. + +- **Implicit vs Explicit Settings Usage**: Recall that if a file named `PSScriptAnalyzerSettings.psd1` is present in the directory you're analyzing, it will be automatically picked up ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). In advanced scenarios, you may intentionally leverage this: + - If you have a repository with many projects, each could have its own settings file in its folder. A top-level build script could just call `Invoke-ScriptAnalyzer -Recurse` on each folder and let each one pick up its own settings implicitly. + - However, be cautious: if multiple settings files are present (say one in a parent folder and one in a subfolder), PSScriptAnalyzer will pick the one in the **exact path you specify**. It doesn't merge or traverse upward to find others. The *explicitly provided* `-Settings` always wins over implicit ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Invoke)). So if you want a single settings to apply to a whole repo, keep one at the root and call PSSA on the root. If you want different ones per sub-project, call PSSA on each sub-project folder separately. + +### Handling Edge Cases and Gotchas + +Even with a well-tuned configuration, you might encounter odd situations: + +- **Settings File Not Detected**: If you run `Invoke-ScriptAnalyzer` and it seems to ignore your settings (e.g., you still see warnings you meant to exclude), double-check: + - The `-Settings` parameter path is correct (if using explicitly). + - If relying on implicit discovery, ensure the file is named exactly `PSScriptAnalyzerSettings.psd1` (capitalization doesn't matter) and that you ran PSSA on the directory that contains that file. + - Make sure the .psd1 has valid PowerShell syntax. If there's a typo (like a missing `@` or a stray character), PSScriptAnalyzer may silently fall back to defaults. You can test by trying to manually dot-source the psd1 file in PowerShell (`. .\PSScriptAnalyzerSettings.psd1`) – it should import as a hashtable without error. If it errors out, fix the syntax. + - Check if there is a rule named **PSScriptAnalyzerSettingsSchema** in your PSSA version. This is a rule under discussion/implementation ([[RULE] PSScriptAnalyzerSettingsSchema · Issue #1279 - GitHub](https://github.com/PowerShell/PSScriptAnalyzer/issues/1279#:~:text=%5BRULE%5D%20PSScriptAnalyzerSettingsSchema%20%C2%B7%20Issue%20,so%20that%20I%20can)) ([[Resolved] PSSCriptAnalyzer warnings in VSCode](https://forums.ironmansoftware.com/t/resolved-psscriptanalyzer-warnings-in-vscode/3602#:~:text=rule%20definitions%20can%20be%20read,com%20%C2%B7%20PowerShell)) that, when enabled, could validate the structure of your settings file. If available, try enabling it to get feedback on your configuration file itself. + +- **Conflicting Settings**: Setting include and exclude rules that overlap, or severity filters that contradict included rules, can lead to confusion. For instance, if you set `Severity = @('Error')` but also `IncludeRules = @('PSAvoidUsingCmdletAliases')` (which is typically a Warning-level rule), you might wonder why you get no output – it's because the rule runs (due to IncludeRules) but its warning results are filtered out by the Severity setting (which allows only Errors). In such cases, PSScriptAnalyzer's logic is that the severity filter is applied after running all rules, effectively discarding any non-matching severities ([Invoke-ScriptAnalyzer (PSScriptAnalyzer) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules#:~:text=You%20can%20specify%20one%20ore,more%20severity%20values)). The takeaway: ensure your rule inclusion/exclusion and severity filters are aligned. Usually, if using Severity filtering, you don't need to list rules of a filtered-out severity in IncludeRules. + +- **Performance Considerations**: In very large projects, running all rules can be slow. You might create a slim settings file for quick iterative checks (excluding some heavy rules or using severity filter to skip informational/style rules), and a full settings file for a thorough check before release. Similarly, if custom rules are slow, you could enable them conditionally. This is more of a pipeline optimization – for example, one could run a "fast lint" on each commit and a "full lint" nightly. + +- **Rule Updates in New PSScriptAnalyzer Versions**: The PSScriptAnalyzer module is periodically updated with new rules or changes. If you update it, new rules (with their default severities) might start running on your code. If you have an **ExcludeRules** list, those new rules will run because you didn’t explicitly exclude them (since they were unknown before). If you use **IncludeRules**, those new rules will *not* run (since your list is explicit). There’s a trade-off: + - Using *ExcludeRules* is future-proof in that you automatically get any new rules (which might be good, to catch new best practices) except ones you excluded. But you might need to update the exclude list if a new rule is noisy or not applicable. + - Using *IncludeRules* gives you a fixed set until you manually update it, preventing surprises from new rules. But you might miss out on beneficial new analysis until you revise the settings. + - **Best practice**: if your goal is to enforce all possible checks and only omit known problematic ones, favor ExcludeRules. If your goal is to enforce a very specific set of rules (compliance scenario, perhaps), use IncludeRules. + +- **Combining with Other Analyzers**: In advanced cases, projects might use multiple linters (e.g., perhaps PSScriptAnalyzer plus a JSON linter for config files, etc.). If using a unified tool like Super-Linter or Mega-Linter, make sure the config file is in the correct location and named as expected for PSScriptAnalyzer. For instance, as noted earlier, Super-Linter expects `.github/linters/.powershell-psscriptanalyzer.psd1`. If you name it differently, you might need to configure the linter to know the custom path (Super-Linter allows some override variables if needed, but following convention is easiest). + +With careful configuration, PSScriptAnalyzer can handle most project requirements. You can mix and match these strategies—just keep track of what you’ve configured so that the behavior remains predictable. + +## Automation and CI/CD Integration + +Integrating PSScriptAnalyzer into your automated build or deployment pipeline is an excellent way to prevent code with issues from being merged or released. Here’s how you can use the hashtable-based settings file in various CI/CD scenarios: + +- **GitHub Actions (Super-Linter)**: GitHub offers a **Super-Linter** action that runs multiple linters, including PSScriptAnalyzer, in one go. If you use Super-Linter, simply placing your settings file at `.github/linters/.powershell-psscriptanalyzer.psd1` in your repo is all you need – the action will automatically detect and use it ([ + PowerShell Gallery + | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview + ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=Documentation%3A%20https%3A%2F%2Fgithub.com%2FPowerShell%2FPSScriptAnalyzer%2Fblob%2Fmaster%2Fdocs%2Fmarkdown%2FInvoke,RecurseCustomRulePath%3D%27path%5Cof%5Ccustomrules%27%20Severity%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3D)). Super-Linter will run PSScriptAnalyzer against your `.ps1`, `.psm1`, etc., files using the rules defined in that file. Ensure that the file is named exactly as expected (note the leading dot and specific name). In your GitHub Actions workflow yaml, you might have: + ```yaml + - uses: github/super-linter@vX.Y.Z + with: + languages: 'POWERSHELL' # (and others as needed) + ``` + That’s it – the configuration is picked up by convention. + +- **GitHub Actions (Dedicated PSSA Action)**: There is also a dedicated action, **microsoft/psscriptanalyzer-action**, which focuses on PSScriptAnalyzer and produces outputs like SARIF (for GitHub code scanning). With this action, you can specify the `settings` input to point to your settings file. For example: + ```yaml + - name: Run PSScriptAnalyzer + uses: microsoft/psscriptanalyzer-action@v1 + with: + path: './**/*.ps1' # or path to your scripts directory + settings: .github/linters/.powershell-psscriptanalyzer.psd1 + failOnError: true # (if you want to fail the job on any errors) + ``` + The `settings` parameter accepts a path to your psd1 file ([GitHub - microsoft/psscriptanalyzer-action: GitHub Action to run PSScriptAnalyzer to your repository and produce a SARIF file](https://github.com/microsoft/psscriptanalyzer-action#:~:text=settings)), so you can keep your config in the repo. This action also supports additional options like producing a SARIF report that integrates with GitHub’s code scanning alerts. Check the action’s documentation for details, such as the `enableExit` flag (which makes the action exit with a non-zero code equal to the number of errors, causing the pipeline to fail if any errors are found) ([GitHub - microsoft/psscriptanalyzer-action: GitHub Action to run PSScriptAnalyzer to your repository and produce a SARIF file](https://github.com/microsoft/psscriptanalyzer-action#:~:text=enableExit)). + +- **Azure Pipelines**: In Azure DevOps or other CI systems, you might not have a pre-built PSScriptAnalyzer task by default, but it's easy to use via PowerShell scripts. For example, in an Azure Pipeline YAML: + ```yaml + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Install-Module PSScriptAnalyzer -Scope CurrentUser -Force + Invoke-ScriptAnalyzer -Path "$(System.DefaultWorkingDirectory)\MyProject" -Recurse -Settings "$(System.DefaultWorkingDirectory)\MyProject\.github\linters\.powershell-psscriptanalyzer.psd1" -ErrorAction Continue + if ($LASTEXITCODE -ne 0) { + Write-Host "PSScriptAnalyzer found errors." + Exit 1 + } + ``` + This installs PSScriptAnalyzer (if not already available on the agent), runs it with the settings file, and uses the exit code or $LASTEXITCODE to decide if the pipeline should fail. You might also capture the results and publish them as artifacts or test results. (Note: By default, `Invoke-ScriptAnalyzer` does not set a distinct exit code for findings; one way to get an exit code is to use the `-ErrorAction` as shown combined with `failOnError`-like logic, or use the PSScriptAnalyzer `EnableExit` switch introduced in newer versions which directly exits with number of errors ([GitHub - microsoft/psscriptanalyzer-action: GitHub Action to run PSScriptAnalyzer to your repository and produce a SARIF file](https://github.com/microsoft/psscriptanalyzer-action#:~:text=enableExit)).) + +- **Other CI Tools**: In Jenkins or other CI systems, the approach is similar: run a PowerShell step that calls PSScriptAnalyzer with the `-Settings` pointing to your file, then parse the output or exit code. PSScriptAnalyzer can output results to the console or to a file (it supports formatting output as plain text, JSON, or even SARIF using the `-OutPath` and `-Format` parameters). For instance, you could output SARIF and feed it into a code analysis tool or archive it for review. + +- **Failing the Build on Violations**: Decide what level of violations should cause a build to fail. A common practice: + - Fail on any "Error" severity issues (these often indicate likely bugs or serious problems). + - Optionally, allow "Warnings" but still succeed the build (maybe just log them), or fail if you want to enforce a stricter standard. + - Ignore "Information" in CI to reduce noise. + + Since your settings file can filter severities, one strategy is to maintain a separate CI-specific settings that sets `Severity = @('Error','Warning')` (if you want to treat warnings as needing attention) or just `'Error'` for very strict pipelines ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=%40%7B%20,Error%27%29)). However, it's often sufficient to use one settings file and handle severity in the pipeline script: e.g., run PSSA with all severities but then post-process results – if any errors, fail; if only warnings, perhaps just warn. This depends on your build governance policy. + +- **Continuous Integration Example**: Suppose we want to ensure no new PSScriptAnalyzer errors/warnings get introduced. We could have a step in CI that runs PSSA and outputs results. If output is not empty (or contains certain severities), mark the build unstable. In GitHub Actions, if using the PSScriptAnalyzer action with `failOnError: true` (or using `enableExit` internally), the action will handle failing on error-level findings automatically ([GitHub - microsoft/psscriptanalyzer-action: GitHub Action to run PSScriptAnalyzer to your repository and produce a SARIF file](https://github.com/microsoft/psscriptanalyzer-action#:~:text=enableExit)). For warnings, you might need a custom step or adjust the action's parameters (some might offer `failOnWarning`). Always consult the specific action/tool docs. + +- **Automated Formatting**: While not exactly analysis, PSScriptAnalyzer includes an `Invoke-Formatter` cmdlet that can auto-format code according to rules. This can be integrated into pre-commit hooks or CI (for example, to fail if code is not formatted). The formatting rules can also be controlled via a settings file (using the `Rules` key for the formatting settings). An advanced CI integration could run the formatter in a PR workflow, and either auto-commit the changes or advise the user to run formatting. This goes hand-in-hand with analysis: analyze to catch issues, format to fix style issues automatically. + +- **SARIF and Security Scanning**: PSScriptAnalyzer isn't a security scanner per se, but it can detect some security relevant patterns (like potential credential leaks, use of dangerous commands, etc.). By outputting to SARIF (Static Analysis Results Interchange Format) and uploading that to platforms like GitHub or Azure, you can integrate PSSA results into code scanning dashboards. The GitHub action we discussed can output SARIF by default, and GitHub Advanced Security can pick it up to show alerts in the Security tab of the repo. + +**Tip:** When integrating into CI, run PSScriptAnalyzer on the **same PowerShell version** that your team uses for development, or the environment you target. The set of rules and their behavior is the same across PS versions for a given PSSA version, but if you use the compatibility rules, the runtime matters (e.g., running on PowerShell 7 analyzing for Windows PowerShell compatibility is fine, but just be aware of what environment the analysis is running in for path references and such). + +By automating PSScriptAnalyzer in CI/CD, you create a quality gate that helps maintain your PowerShell code standard. The settings file ensures this is done consistently every time. As developers push code, they get immediate feedback if something doesn't meet the team's standards, and they can fix it before merging – resulting in cleaner, more reliable code in your main branches. + +## Best Practices + +To wrap up, here are some best practices and tips for using a hashtable-based PSScriptAnalyzer settings file effectively: + +- **Start with Defaults, Then Tweak**: Begin by running PSScriptAnalyzer with the default rules on your codebase to see what it flags. Use that to inform your settings. Disable rules only if you have a good reason. It's better to be informed of an issue and decide to ignore it than to never know it at all. That said, if a rule consistently flags things that are acceptable in your context, exclude it in the settings to reduce noise. + +- **Use Exclusions Sparingly**: Each excluded rule is a class of issues you won't hear about. Keep the **ExcludeRules** list as short as possible. For instance, you might exclude style preferences you don't agree with, but think twice before excluding rules related to security or correctness. If a rule is noisy but important, consider fixing the underlying code instead of excluding the rule. + +- **Leverage Severity for Focus**: Especially in CI, consider filtering out informational messages. Many teams configure `Severity = @('Error','Warning')` in the settings (or only errors) ([ + PowerShell Gallery + | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview + ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=Severity%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3D%C2%A0%40%28%20%27Error%27%20%27Warning%27%20%29%20IncludeDefaultRules%C2%A0%3D%C2%A0%24,%27PSUseShouldProcessForStateChangingFunctions%27%2C%20%27PSAvoidUsingConvertToSecureStringWithPlainText)) to focus on the more significant issues. You can still run a full analysis locally with all severities if you want the additional info. Another approach is to use severity filtering only in CI (with a separate settings or `-Severity` param) while keeping the dev-time analysis fully verbose. + +- **Keep the Settings File with the Code**: Store the `.psd1` in your repository (as we did under `.github/linters/` or at the project root). This way, changes to it are versioned. When updating PSScriptAnalyzer or altering rules, any adjustments you make to the configuration travel along with those changes in source control. New developers pulling the repo will get the config and their VSCode can pick it up, etc. + +- **Document Your Choices**: Within the settings file, use comments to note why certain rules are excluded or certain settings are in place. This helps future maintainers understand the rationale. The psd1 supports comments (as it's just text), as shown in the VSCode example settings file with lots of commented explanations ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=%40%7B%20,Severity%20%3D%20%40%28%27Error%27%2C%27Warning)) ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,ExcludeRules%20%3D%20%40%28%27PSAvoidUsingWriteHost%27%2C%27PSMissingModuleManifestField)). Don’t hesitate to annotate it. + +- **Validate Custom Rules**: If you wrote custom rules, include unit tests for them if possible (for example, using Pester to run the rule function on sample ASTs). This ensures your custom rules work as expected and continue to work when you update PSScriptAnalyzer (or PowerShell itself). Also, if sharing custom rules with others, provide documentation for those rules. + +- **Regularly Update PSScriptAnalyzer**: New versions may bring improvements and new rules. Keep an eye on the PSScriptAnalyzer release notes. When updating, run it on your code with the new version in a non-blocking way to see if new warnings/errors appear. Adjust your code or settings accordingly. For example, if a new rule flags something you intentionally do, you might add that rule to ExcludeRules after upgrading. + +- **Use CI to Enforce Standards**: Make the CI builds fail on PSScriptAnalyzer errors (and optionally on warnings). This creates an incentive to keep code clean. However, avoid making it so strict that it impedes progress – find a balance. Often teams treat warnings as "should fix" but not blockers, while errors are "must fix". If so, set up your pipeline accordingly (fail on errors, and maybe allow warnings but still report them). + +- **Troubleshooting Tips**: + - If you think the settings file isn't working, run `Invoke-ScriptAnalyzer` with the `-Verbose` flag. It will often print information about which settings file is loaded (or if none) and what rules it's executing. This can reveal, for example, that it's not finding your file, or a rule name was not recognized. + - Ensure no duplicate keys or conflicting keys in the hashtable. Each key should appear at most once. If you accidentally have two `Severity` entries, for example, the latter might overwrite the former or the file might fail to parse. + - Remember that the settings file is essentially a PowerShell script that returns a hashtable. This means you could even have logic in it (though not recommended for simplicity and security). For instance, technically you could do something like: + ```powershell + @{ + Severity = $([Environment]::GetEnvironmentVariable('PSSASeverity') -split ',') + } + ``` + This is advanced usage and generally discouraged, but it's worth noting the file is evaluated by PowerShell. Most will keep it static. + + - On rare occasions, a rule might throw an unexpected error when running on certain code (an edge case bug in PSScriptAnalyzer). If you encounter this, you can exclude that rule as a workaround and report the issue to the PSScriptAnalyzer project. Use `-ExcludeRule` on the command as a quick test to identify which rule is problematic, then permanently exclude in settings until a fix is available. + +- **Security of the Settings File**: Treat the settings file as part of your codebase. It doesn't typically contain secrets or anything sensitive (unless you, say, whitelist some secret in it which you shouldn't). However, because it can theoretically execute code (like any .psd1), ensure only trusted persons can modify it, especially in environments where it might run automatically (CI). In practice, keep it in source control and use code reviews for changes. + +By following these best practices, you can harness PSScriptAnalyzer to its fullest, maintaining high quality PowerShell code with a configuration that suits your team's needs. A well-tuned settings file becomes a powerful ally in your development process – catching issues early, enforcing standards, and even educating team members about best practices (since each rule often comes with guidance on how to improve the code). + +**References:** The information in this guide was compiled from official PSScriptAnalyzer documentation and community sources. Key references include the Microsoft Learn docs for PSScriptAnalyzer ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=The%20following%20example%20excludes%20two,other%20than%20Error%20and%20Warning)) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)), the PSScriptAnalyzer GitHub README ([PSScriptAnalyzer/README.md at main · PowerShell/PSScriptAnalyzer · GitHub](https://github.com/PowerShell/PSScriptAnalyzer/blob/master/README.md#:~:text=PSScriptAnalyzer%20is%20a%20static%20code,suggests%20possible%20solutions%20for%20improvements)), example settings files provided by the PowerShell extension ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=%40%7B%20,Severity%20%3D%20%40%28%27Error%27%2C%27Warning)) ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,ExcludeRules%20%3D%20%40%28%27PSAvoidUsingWriteHost%27%2C%27PSMissingModuleManifestField)), and community blog posts about custom rules ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=%40,SqlServerDsc.AnalyzerRules.psm1%27)) and CI integration. These resources offer deeper insights and examples for those looking to further explore PSScriptAnalyzer's capabilities. Enjoy clean, robust PowerShell scripting with PSScriptAnalyzer! diff --git a/LICENSE b/LICENSE index 9a9cbf4..75789b6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2025 PSModule - -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. +MIT License + +Copyright (c) 2025 PSModule + +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. diff --git a/action.yml b/action.yml index 90768f1..b5c2f66 100644 --- a/action.yml +++ b/action.yml @@ -1,27 +1,46 @@ -name: '{{ NAME }}' -description: '{{ DESCRIPTION }}' +name: Test-PSModule (by PSModule) +description: Test a PowerShell module before publishing the module to the PowerShell Gallery. author: PSModule branding: - icon: upload-cloud - color: white + icon: check-square + color: gray-dark inputs: - working-directory: - description: The working directory where Terraform will be executed + Path: + description: The path to the code to test. + required: true + default: ${{ github.workspace }} + Settings: + description: The type of tests to run. Can be either 'Module', 'SourceCode' or 'Custom'. required: false - subject: - description: The subject to greet + default: 'Custom' + SettingsPath: + description: The path to the settings file. required: false - default: World + default: ${{ github.workspace }}/.github/linters/.powershell-psscriptanalyzer.psd1 + +outputs: + passed: + description: If the tests passed. + value: ${{ steps.test.outputs.Passed }} runs: using: composite steps: - - name: '{{ NAME }}' - uses: PSModule/GitHub-Script@v1 + - name: Get test paths + uses: PSModule/Github-Script@v1 + id: paths env: - GITHUB_ACTION_INPUT_subject: ${{ inputs.subject }} + GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Path: ${{ inputs.Path }} + GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Settings: ${{ inputs.Settings }} + GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_SettingsPath: ${{ inputs.SettingsPath }} + with: + Script: ${{ github.action_path }}/scripts/main.ps1 + + - name: Invoke-Pester + uses: PSModule/Invoke-Pester@fix + id: test with: - Script: | - # '{{ NAME }}' - ${{ github.action_path }}\scripts\main.ps1 + TestResult_TestSuiteName: PSScriptAnalyzer + Path: ${{ fromJson(steps.paths.outputs.result).TestPath }} + Run_Path: ${{ inputs.Path }} diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 2779f38..4b78ea1 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -1,24 +1,27 @@ -#Requires -Modules GitHub - -[CmdletBinding()] -param( - [Parameter()] - [string] $Subject = $env:GITHUB_ACTION_INPUT_subject -) - -begin { - $scriptName = $MyInvocation.MyCommand.Name - Write-Debug "[$scriptName] - Start" -} - -process { - try { - Write-Output "Hello, $Subject!" - } catch { - throw $_ - } -} - -end { - Write-Debug "[$scriptName] - End" -} +# If test type is module, the code we ought to test is in the path/name folder, otherwise it's in the path folder. +$settings = $env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Settings +$testPath = Resolve-Path -Path "$PSScriptRoot/PSScriptAnalyzer" | Select-Object -ExpandProperty Path +$codePath = Resolve-Path -Path $env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Path | Select-Object -ExpandProperty Path +$settingsPath = switch -Regex ($settings) { + 'Module|SourceCode' { + "$testPath/$settings.Settings.psd1" + } + 'Custom' { + Resolve-Path -Path "$env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_SettingsPath" | Select-Object -ExpandProperty Path + } + default { + throw "Invalid test type: [$settings]" + } +} + +[pscustomobject]@{ + Settings = $settings + CodePath = $codePath + TestPath = $testPath + SettingsPath = $settingsPath +} | Format-List + +Set-GitHubOutput -Name Settings -Value $settings +Set-GitHubOutput -Name CodePath -Value $codePath +Set-GitHubOutput -Name TestPath -Value $testPath +Set-GitHubOutput -Name SettingsPath -Value $settingsPath diff --git a/scripts/tests/PSScriptAnalyzer/Module.Settings.psd1 b/scripts/tests/PSScriptAnalyzer/Module.Settings.psd1 new file mode 100644 index 0000000..62a5818 --- /dev/null +++ b/scripts/tests/PSScriptAnalyzer/Module.Settings.psd1 @@ -0,0 +1,53 @@ +@{ + Rules = @{ + PSAlignAssignmentStatement = @{ + Enable = $true + CheckHashtable = $true + } + PSAvoidLongLines = @{ + Enable = $true + MaximumLineLength = 150 + } + PSAvoidSemicolonsAsLineTerminators = @{ + Enable = $true + } + PSPlaceCloseBrace = @{ + Enable = $true + NewLineAfter = $false + IgnoreOneLineBlock = $true + NoEmptyLineBefore = $false + } + PSPlaceOpenBrace = @{ + Enable = $true + OnSameLine = $true + NewLineAfter = $true + IgnoreOneLineBlock = $true + } + PSProvideCommentHelp = @{ + Enable = $true + ExportedOnly = $false + BlockComment = $true + VSCodeSnippetCorrection = $false + Placement = 'begin' + } + PSUseConsistentIndentation = @{ + Enable = $true + IndentationSize = 4 + PipelineIndentation = 'IncreaseIndentationForFirstPipeline' + Kind = 'space' + } + PSUseConsistentWhitespace = @{ + Enable = $true + CheckInnerBrace = $true + CheckOpenBrace = $true + CheckOpenParen = $true + CheckOperator = $true + CheckPipe = $true + CheckPipeForRedundantWhitespace = $true + CheckSeparator = $true + CheckParameter = $true + IgnoreAssignmentOperatorInsideHashTable = $true + } + } + ExcludeRules = @() +} diff --git a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Configuration.ps1 b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Configuration.ps1 new file mode 100644 index 0000000..e495085 --- /dev/null +++ b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Configuration.ps1 @@ -0,0 +1,5 @@ +@{ + Output = @{ + Verbosity = 'Detailed' + } +} diff --git a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Container.ps1 b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Container.ps1 new file mode 100644 index 0000000..7d1e654 --- /dev/null +++ b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Container.ps1 @@ -0,0 +1,9 @@ +@{ + Path = Get-ChildItem -Path $PSScriptRoot -Filter *.Tests.ps1 | Select-Object -ExpandProperty FullName + Data = @{ + Path = "$env:GITHUB_ACTION_INPUT_Run_Path/output/modules/$env:ModuleName" + SettingsFilePath = "$PSScriptRoot/PSScriptAnalyzer.Settings.psd1" + Debug = $false + Verbose = $false + } +} diff --git a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 new file mode 100644 index 0000000..6fb251e --- /dev/null +++ b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 @@ -0,0 +1,71 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', 'Path', + Justification = 'Path is being used.' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', 'SettingsFilePath', + Justification = 'SettingsFilePath is being used.' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', 'relativeSettingsFilePath', + Justification = 'relativeSettingsFilePath is being used.' +)] +[CmdLetBinding()] +Param( + [Parameter(Mandatory)] + [string] $Path, + + [Parameter(Mandatory)] + [string] $SettingsFilePath +) + +BeforeDiscovery { + $settings = Import-PowerShellDataFile -Path $SettingsFilePath + $rules = [Collections.Generic.List[System.Collections.Specialized.OrderedDictionary]]::new() + $ruleObjects = Get-ScriptAnalyzerRule -Verbose:$false | Sort-Object -Property Severity, CommonName + $Severeties = $ruleObjects | Select-Object -ExpandProperty Severity -Unique + foreach ($ruleObject in $ruleObjects) { + $rules.Add( + [ordered]@{ + RuleName = $ruleObject.RuleName + CommonName = $ruleObject.CommonName + Severity = $ruleObject.Severity + Description = $ruleObject.Description + Skip = $ruleObject.RuleName -in $settings.ExcludeRules + <# + RuleName : PSDSCUseVerboseMessageInDSCResource + CommonName : Use verbose message in DSC resource + Description : It is a best practice to emit informative, verbose messages in DSC resource functions. This helps in debugging issues when a DSC configuration is executed. + SourceType : Builtin + SourceName : PSDSC + Severity : Information + ImplementingType : Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.UseVerboseMessageInDSCResource + #> + } + ) + } + Write-Warning "Discovered [$($rules.Count)] rules" + $relativeSettingsFilePath = $SettingsFilePath.Replace($PSScriptRoot, '').Trim('\').Trim('/') +} + +Describe "PSScriptAnalyzer tests using settings file [$relativeSettingsFilePath]" { + BeforeAll { + $testResults = Invoke-ScriptAnalyzer -Path $Path -Settings $SettingsFilePath -Recurse -Verbose:$false + Write-Warning "Found [$($testResults.Count)] issues" + } + + foreach ($Severety in $Severeties) { + Context "Severity: $Severety" { + foreach ($rule in $rules | Where-Object -Property Severity -EQ $Severety) { + It "$($rule.CommonName) ($($rule.RuleName))" -Skip:$rule.Skip { + $issues = [Collections.Generic.List[string]]::new() + $testResults | Where-Object -Property RuleName -EQ $rule.RuleName | ForEach-Object { + $relativePath = $_.ScriptPath.Replace($Path, '').Trim('\').Trim('/') + $issues.Add(([Environment]::NewLine + " - $relativePath`:L$($_.Line):C$($_.Column)")) + } + $issues -join '' | Should -BeNullOrEmpty -Because $rule.Description + } + } + } + } +} diff --git a/scripts/tests/PSScriptAnalyzer/SourceCode.Settings.psd1 b/scripts/tests/PSScriptAnalyzer/SourceCode.Settings.psd1 new file mode 100644 index 0000000..09cc3d0 --- /dev/null +++ b/scripts/tests/PSScriptAnalyzer/SourceCode.Settings.psd1 @@ -0,0 +1,56 @@ +@{ + Rules = @{ + PSAlignAssignmentStatement = @{ + Enable = $true + CheckHashtable = $true + } + PSAvoidLongLines = @{ + Enable = $true + MaximumLineLength = 150 + } + PSAvoidSemicolonsAsLineTerminators = @{ + Enable = $true + } + PSPlaceCloseBrace = @{ + Enable = $true + NewLineAfter = $false + IgnoreOneLineBlock = $true + NoEmptyLineBefore = $false + } + PSPlaceOpenBrace = @{ + Enable = $true + OnSameLine = $true + NewLineAfter = $true + IgnoreOneLineBlock = $true + } + PSProvideCommentHelp = @{ + Enable = $true + ExportedOnly = $false + BlockComment = $true + VSCodeSnippetCorrection = $false + Placement = 'begin' + } + PSUseConsistentIndentation = @{ + Enable = $true + IndentationSize = 4 + PipelineIndentation = 'IncreaseIndentationForFirstPipeline' + Kind = 'space' + } + PSUseConsistentWhitespace = @{ + Enable = $true + CheckInnerBrace = $true + CheckOpenBrace = $true + CheckOpenParen = $true + CheckOperator = $true + CheckPipe = $true + CheckPipeForRedundantWhitespace = $true + CheckSeparator = $true + CheckParameter = $true + IgnoreAssignmentOperatorInsideHashTable = $true + } + } + ExcludeRules = @( + 'PSMissingModuleManifestField', # This rule is not applicable until the module is built. + 'PSUseToExportFieldsInManifest' + ) +} diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index a570e4d..0000000 --- a/tests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Placeholder for tests - -Location for tests of the action. diff --git a/tests/outputTestRepo/outputs/docs/PSModuleTest/Get-PSModuleTest.md b/tests/outputTestRepo/outputs/docs/PSModuleTest/Get-PSModuleTest.md new file mode 100644 index 0000000..346f9b9 --- /dev/null +++ b/tests/outputTestRepo/outputs/docs/PSModuleTest/Get-PSModuleTest.md @@ -0,0 +1,72 @@ +--- +external help file: PSModuleTest-help.xml +Module Name: PSModuleTest +online version: +schema: 2.0.0 +--- + +# Get-PSModuleTest + +## SYNOPSIS +Performs tests on a module. + +## SYNTAX + +```powershell +Get-PSModuleTest [-Name] [-ProgressAction ] [] +``` + +## DESCRIPTION +{{ Fill in the Description }} + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Test-PSModule -Name 'World' +``` + +"Hello, World!" + +## PARAMETERS + +### -Name +Name of the person to greet. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS diff --git a/tests/outputTestRepo/outputs/docs/PSModuleTest/New-PSModuleTest.md b/tests/outputTestRepo/outputs/docs/PSModuleTest/New-PSModuleTest.md new file mode 100644 index 0000000..e3ce5e7 --- /dev/null +++ b/tests/outputTestRepo/outputs/docs/PSModuleTest/New-PSModuleTest.md @@ -0,0 +1,72 @@ +--- +external help file: PSModuleTest-help.xml +Module Name: PSModuleTest +online version: +schema: 2.0.0 +--- + +# New-PSModuleTest + +## SYNOPSIS +Performs tests on a module. + +## SYNTAX + +```powershell +New-PSModuleTest [-Name] [-ProgressAction ] [] +``` + +## DESCRIPTION +{{ Fill in the Description }} + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Test-PSModule -Name 'World' +``` + +"Hello, World!" + +## PARAMETERS + +### -Name +Name of the person to greet. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS diff --git a/tests/outputTestRepo/outputs/docs/PSModuleTest/Set-PSModuleTest.md b/tests/outputTestRepo/outputs/docs/PSModuleTest/Set-PSModuleTest.md new file mode 100644 index 0000000..6504d6f --- /dev/null +++ b/tests/outputTestRepo/outputs/docs/PSModuleTest/Set-PSModuleTest.md @@ -0,0 +1,72 @@ +--- +external help file: PSModuleTest-help.xml +Module Name: PSModuleTest +online version: +schema: 2.0.0 +--- + +# Set-PSModuleTest + +## SYNOPSIS +Performs tests on a module. + +## SYNTAX + +```powershell +Set-PSModuleTest [-Name] [-ProgressAction ] [] +``` + +## DESCRIPTION +{{ Fill in the Description }} + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Test-PSModule -Name 'World' +``` + +"Hello, World!" + +## PARAMETERS + +### -Name +Name of the person to greet. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS diff --git a/tests/outputTestRepo/outputs/docs/PSModuleTest/Test-PSModuleTest.md b/tests/outputTestRepo/outputs/docs/PSModuleTest/Test-PSModuleTest.md new file mode 100644 index 0000000..59fc9f8 --- /dev/null +++ b/tests/outputTestRepo/outputs/docs/PSModuleTest/Test-PSModuleTest.md @@ -0,0 +1,72 @@ +--- +external help file: PSModuleTest-help.xml +Module Name: PSModuleTest +online version: +schema: 2.0.0 +--- + +# Test-PSModuleTest + +## SYNOPSIS +Performs tests on a module. + +## SYNTAX + +```powershell +Test-PSModuleTest [-Name] [-ProgressAction ] [] +``` + +## DESCRIPTION +{{ Fill in the Description }} + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Test-PSModule -Name 'World' +``` + +"Hello, World!" + +## PARAMETERS + +### -Name +Name of the person to greet. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +## OUTPUTS + +## NOTES + +## RELATED LINKS diff --git a/tests/outputTestRepo/outputs/modules/PSModuleTest/PSModuleTest.psd1 b/tests/outputTestRepo/outputs/modules/PSModuleTest/PSModuleTest.psd1 new file mode 100644 index 0000000..ebe988d --- /dev/null +++ b/tests/outputTestRepo/outputs/modules/PSModuleTest/PSModuleTest.psd1 @@ -0,0 +1,75 @@ +@{ + RootModule = 'PSModuleTest.psm1' + ModuleVersion = '999.0.0' + CompatiblePSEditions = @( + 'Core' + 'Desktop' + ) + GUID = '20b37221-db1c-43db-9cca-f22b33123548' + Author = 'PSModule' + CompanyName = 'PSModule' + Copyright = '(c) 2024 PSModule. All rights reserved.' + Description = 'Process a module from source code to published module.' + PowerShellVersion = '5.1' + ProcessorArchitecture = 'None' + RequiredModules = @( + @{ + ModuleVersion = '1.0' + ModuleName = 'PSSemVer' + } + 'Utilities' + ) + RequiredAssemblies = 'assemblies/LsonLib.dll' + ScriptsToProcess = 'scripts/loader.ps1' + TypesToProcess = @( + 'types/DirectoryInfo.Types.ps1xml' + 'types/FileInfo.Types.ps1xml' + ) + FormatsToProcess = @( + 'formats/CultureInfo.Format.ps1xml' + 'formats/Mygciview.Format.ps1xml' + ) + NestedModules = @( + 'modules/OtherPSModule.psm1' + ) + FunctionsToExport = @( + 'Get-PSModuleTest' + 'New-PSModuleTest' + 'Set-PSModuleTest' + 'Test-PSModuleTest' + ) + CmdletsToExport = @() + VariablesToExport = @() + AliasesToExport = '*' + ModuleList = @( + 'modules/OtherPSModule.psm1' + ) + FileList = @( + 'PSModuleTest.psd1' + 'PSModuleTest.psm1' + 'assemblies/LsonLib.dll' + 'data/Config.psd1' + 'data/Settings.psd1' + 'formats/CultureInfo.Format.ps1xml' + 'formats/Mygciview.Format.ps1xml' + 'modules/OtherPSModule.psm1' + 'scripts/loader.ps1' + 'types/DirectoryInfo.Types.ps1xml' + 'types/FileInfo.Types.ps1xml' + ) + PrivateData = @{ + PSData = @{ + Tags = @( + 'workflow' + 'powershell' + 'powershell-module' + 'PSEdition_Desktop' + 'PSEdition_Core' + ) + LicenseUri = 'https://github.com/PSModule/Process-PSModule/blob/main/LICENSE' + ProjectUri = 'https://github.com/PSModule/Process-PSModule' + IconUri = 'https://raw.githubusercontent.com/PSModule/Process-PSModule/main/icon/icon.png' + } + } +} + diff --git a/tests/outputTestRepo/outputs/modules/PSModuleTest/PSModuleTest.psm1 b/tests/outputTestRepo/outputs/modules/PSModuleTest/PSModuleTest.psm1 new file mode 100644 index 0000000..3553dd9 --- /dev/null +++ b/tests/outputTestRepo/outputs/modules/PSModuleTest/PSModuleTest.psm1 @@ -0,0 +1,392 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] +[CmdletBinding()] +param() + +$scriptName = $MyInvocation.MyCommand.Name +Write-Verbose "[$scriptName] Importing module" + +#region - Data import +Write-Verbose "[$scriptName] - [data] - Processing folder" +$dataFolder = (Join-Path $PSScriptRoot 'data') +Write-Verbose "[$scriptName] - [data] - [$dataFolder]" +Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object { + Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Importing" + New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force + Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Done" +} + +Write-Verbose "[$scriptName] - [data] - Done" +#endregion - Data import + +#region - From /init +Write-Verbose "[$scriptName] - [/init] - Processing folder" + +#region - From /init/initializer.ps1 +Write-Verbose "[$scriptName] - [/init/initializer.ps1] - Importing" + +Write-Verbose '-------------------------------' +Write-Verbose '--- THIS IS AN INITIALIZER ---' +Write-Verbose '-------------------------------' + +Write-Verbose "[$scriptName] - [/init/initializer.ps1] - Done" +#endregion - From /init/initializer.ps1 + +Write-Verbose "[$scriptName] - [/init] - Done" +#endregion - From /init + +#region - From /classes +Write-Verbose "[$scriptName] - [/classes] - Processing folder" + +#region - From /classes/Book.ps1 +Write-Verbose "[$scriptName] - [/classes/Book.ps1] - Importing" + +class Book { + # Class properties + [string] $Title + [string] $Author + [string] $Synopsis + [string] $Publisher + [datetime] $PublishDate + [int] $PageCount + [string[]] $Tags + # Default constructor + Book() { $this.Init(@{}) } + # Convenience constructor from hashtable + Book([hashtable]$Properties) { $this.Init($Properties) } + # Common constructor for title and author + Book([string]$Title, [string]$Author) { + $this.Init(@{Title = $Title; Author = $Author }) + } + # Shared initializer method + [void] Init([hashtable]$Properties) { + foreach ($Property in $Properties.Keys) { + $this.$Property = $Properties.$Property + } + } + # Method to calculate reading time as 2 minutes per page + [timespan] GetReadingTime() { + if ($this.PageCount -le 0) { + throw 'Unable to determine reading time from page count.' + } + $Minutes = $this.PageCount * 2 + return [timespan]::new(0, $Minutes, 0) + } + # Method to calculate how long ago a book was published + [timespan] GetPublishedAge() { + if ( + $null -eq $this.PublishDate -or + $this.PublishDate -eq [datetime]::MinValue + ) { throw 'PublishDate not defined' } + + return (Get-Date) - $this.PublishDate + } + # Method to return a string representation of the book + [string] ToString() { + return "$($this.Title) by $($this.Author) ($($this.PublishDate.Year))" + } +} + +Write-Verbose "[$scriptName] - [/classes/Book.ps1] - Done" +#endregion - From /classes/Book.ps1 +#region - From /classes/BookList.ps1 +Write-Verbose "[$scriptName] - [/classes/BookList.ps1] - Importing" + +class BookList { + # Static property to hold the list of books + static [System.Collections.Generic.List[Book]] $Books + # Static method to initialize the list of books. Called in the other + # static methods to avoid needing to explicit initialize the value. + static [void] Initialize() { [BookList]::Initialize($false) } + static [bool] Initialize([bool]$force) { + if ([BookList]::Books.Count -gt 0 -and -not $force) { + return $false + } + + [BookList]::Books = [System.Collections.Generic.List[Book]]::new() + + return $true + } + # Ensure a book is valid for the list. + static [void] Validate([book]$Book) { + $Prefix = @( + 'Book validation failed: Book must be defined with the Title,' + 'Author, and PublishDate properties, but' + ) -join ' ' + if ($null -eq $Book) { throw "$Prefix was null" } + if ([string]::IsNullOrEmpty($Book.Title)) { + throw "$Prefix Title wasn't defined" + } + if ([string]::IsNullOrEmpty($Book.Author)) { + throw "$Prefix Author wasn't defined" + } + if ([datetime]::MinValue -eq $Book.PublishDate) { + throw "$Prefix PublishDate wasn't defined" + } + } + # Static methods to manage the list of books. + # Add a book if it's not already in the list. + static [void] Add([Book]$Book) { + [BookList]::Initialize() + [BookList]::Validate($Book) + if ([BookList]::Books.Contains($Book)) { + throw "Book '$Book' already in list" + } + + $FindPredicate = { + param([Book]$b) + + $b.Title -eq $Book.Title -and + $b.Author -eq $Book.Author -and + $b.PublishDate -eq $Book.PublishDate + }.GetNewClosure() + if ([BookList]::Books.Find($FindPredicate)) { + throw "Book '$Book' already in list" + } + + [BookList]::Books.Add($Book) + } + # Clear the list of books. + static [void] Clear() { + [BookList]::Initialize() + [BookList]::Books.Clear() + } + # Find a specific book using a filtering scriptblock. + static [Book] Find([scriptblock]$Predicate) { + [BookList]::Initialize() + return [BookList]::Books.Find($Predicate) + } + # Find every book matching the filtering scriptblock. + static [Book[]] FindAll([scriptblock]$Predicate) { + [BookList]::Initialize() + return [BookList]::Books.FindAll($Predicate) + } + # Remove a specific book. + static [void] Remove([Book]$Book) { + [BookList]::Initialize() + [BookList]::Books.Remove($Book) + } + # Remove a book by property value. + static [void] RemoveBy([string]$Property, [string]$Value) { + [BookList]::Initialize() + $Index = [BookList]::Books.FindIndex({ + param($b) + $b.$Property -eq $Value + }.GetNewClosure()) + if ($Index -ge 0) { + [BookList]::Books.RemoveAt($Index) + } + } +} + +Write-Verbose "[$scriptName] - [/classes/BookList.ps1] - Done" +#endregion - From /classes/BookList.ps1 + +Write-Verbose "[$scriptName] - [/classes] - Done" +#endregion - From /classes + +#region - From /private +Write-Verbose "[$scriptName] - [/private] - Processing folder" + +#region - From /private/Get-InternalPSModule.ps1 +Write-Verbose "[$scriptName] - [/private/Get-InternalPSModule.ps1] - Importing" + +Function Get-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} + +Write-Verbose "[$scriptName] - [/private/Get-InternalPSModule.ps1] - Done" +#endregion - From /private/Get-InternalPSModule.ps1 +#region - From /private/Set-InternalPSModule.ps1 +Write-Verbose "[$scriptName] - [/private/Set-InternalPSModule.ps1] - Importing" + +Function Set-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} + +Write-Verbose "[$scriptName] - [/private/Set-InternalPSModule.ps1] - Done" +#endregion - From /private/Set-InternalPSModule.ps1 + +Write-Verbose "[$scriptName] - [/private] - Done" +#endregion - From /private + +#region - From /public +Write-Verbose "[$scriptName] - [/public] - Processing folder" + +#region - From /public/Get-PSModuleTest.ps1 +Write-Verbose "[$scriptName] - [/public/Get-PSModuleTest.ps1] - Importing" + +#Requires -Modules Utilities + +function Get-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Debug 'Debug message' + Write-Verbose 'Verbose message' + Write-Output "Hello, $Name!" +} + +Write-Verbose "[$scriptName] - [/public/Get-PSModuleTest.ps1] - Done" +#endregion - From /public/Get-PSModuleTest.ps1 +#region - From /public/New-PSModuleTest.ps1 +Write-Verbose "[$scriptName] - [/public/New-PSModuleTest.ps1] - Importing" + +#Requires -Modules @{ModuleName='PSSemVer'; ModuleVersion='1.0'} + +function New-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Debug 'Debug message' + Write-Verbose 'Verbose message' + Write-Output "Hello, $Name!" +} + +Write-Verbose "[$scriptName] - [/public/New-PSModuleTest.ps1] - Done" +#endregion - From /public/New-PSModuleTest.ps1 +#region - From /public/Set-PSModuleTest.ps1 +Write-Verbose "[$scriptName] - [/public/Set-PSModuleTest.ps1] - Importing" + +function Set-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding(SupportsShouldProcess)] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Debug 'Debug message' + Write-Verbose 'Verbose message' + if ($PSCmdlet.ShouldProcess($Name, 'Set-PSModuleTest')) { + Write-Output "Hello, $Name!" + } +} + +Write-Verbose "[$scriptName] - [/public/Set-PSModuleTest.ps1] - Done" +#endregion - From /public/Set-PSModuleTest.ps1 +#region - From /public/Test-PSModuleTest.ps1 +Write-Verbose "[$scriptName] - [/public/Test-PSModuleTest.ps1] - Importing" + +function Test-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Debug 'Debug message' + Write-Verbose 'Verbose message' + Write-Output "Hello, $Name!" +} + +Write-Verbose "[$scriptName] - [/public/Test-PSModuleTest.ps1] - Done" +#endregion - From /public/Test-PSModuleTest.ps1 + +Write-Verbose "[$scriptName] - [/public] - Done" +#endregion - From /public + +#region - From /finally.ps1 +Write-Verbose "[$scriptName] - [/finally.ps1] - Importing" + +Write-Verbose '------------------------------' +Write-Verbose '--- THIS IS A LAST LOADER ---' +Write-Verbose '------------------------------' +Write-Verbose "[$scriptName] - [/finally.ps1] - Done" +#endregion - From /finally.ps1 + +$exports = @{ + Cmdlet = '' + Alias = '*' + Variable = '' + Function = @( + 'Get-PSModuleTest' + 'New-PSModuleTest' + 'Set-PSModuleTest' + 'Test-PSModuleTest' + ) +} +Export-ModuleMember @exports + diff --git a/tests/outputTestRepo/outputs/modules/PSModuleTest/assemblies/LsonLib.dll b/tests/outputTestRepo/outputs/modules/PSModuleTest/assemblies/LsonLib.dll new file mode 100644 index 0000000000000000000000000000000000000000..36618070d5c9f5131ec66720aa0565c13e86d23f GIT binary patch literal 43520 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 + + + + System.Globalization.CultureInfo + + System.Globalization.CultureInfo + + + + + 16 + + + 16 + + + + + + + + LCID + + + Name + + + DisplayName + + + + + + + + diff --git a/tests/outputTestRepo/outputs/modules/PSModuleTest/formats/Mygciview.Format.ps1xml b/tests/outputTestRepo/outputs/modules/PSModuleTest/formats/Mygciview.Format.ps1xml new file mode 100644 index 0000000..4c972c2 --- /dev/null +++ b/tests/outputTestRepo/outputs/modules/PSModuleTest/formats/Mygciview.Format.ps1xml @@ -0,0 +1,65 @@ + + + + + mygciview + + System.IO.DirectoryInfo + System.IO.FileInfo + + + PSParentPath + + + + + + 7 + Left + + + + 26 + Right + + + + 26 + Right + + + + 14 + Right + + + + Left + + + + + + + + ModeWithoutHardLink + + + LastWriteTime + + + CreationTime + + + Length + + + Name + + + + + + + + diff --git a/tests/outputTestRepo/outputs/modules/PSModuleTest/modules/OtherPSModule.psm1 b/tests/outputTestRepo/outputs/modules/PSModuleTest/modules/OtherPSModule.psm1 new file mode 100644 index 0000000..9e4353b --- /dev/null +++ b/tests/outputTestRepo/outputs/modules/PSModuleTest/modules/OtherPSModule.psm1 @@ -0,0 +1,19 @@ +Function Get-OtherPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .DESCRIPTION + A longer description of the function. + + .EXAMPLE + Get-OtherPSModule -Name 'World' + #> + [CmdletBinding()] + param( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/outputTestRepo/outputs/modules/PSModuleTest/scripts/loader.ps1 b/tests/outputTestRepo/outputs/modules/PSModuleTest/scripts/loader.ps1 new file mode 100644 index 0000000..973735a --- /dev/null +++ b/tests/outputTestRepo/outputs/modules/PSModuleTest/scripts/loader.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------' +Write-Verbose '--- THIS IS A LOADER ---' +Write-Verbose '-------------------------' diff --git a/tests/outputTestRepo/outputs/modules/PSModuleTest/types/DirectoryInfo.Types.ps1xml b/tests/outputTestRepo/outputs/modules/PSModuleTest/types/DirectoryInfo.Types.ps1xml new file mode 100644 index 0000000..aef538b --- /dev/null +++ b/tests/outputTestRepo/outputs/modules/PSModuleTest/types/DirectoryInfo.Types.ps1xml @@ -0,0 +1,21 @@ + + + + System.IO.FileInfo + + + Status + Success + + + + + System.IO.DirectoryInfo + + + Status + Success + + + + diff --git a/tests/outputTestRepo/outputs/modules/PSModuleTest/types/FileInfo.Types.ps1xml b/tests/outputTestRepo/outputs/modules/PSModuleTest/types/FileInfo.Types.ps1xml new file mode 100644 index 0000000..4cfaf6b --- /dev/null +++ b/tests/outputTestRepo/outputs/modules/PSModuleTest/types/FileInfo.Types.ps1xml @@ -0,0 +1,14 @@ + + + + System.IO.FileInfo + + + Age + + ((Get-Date) - ($this.CreationTime)).Days + + + + + diff --git a/tests/srcTestRepo/README.md b/tests/srcTestRepo/README.md new file mode 100644 index 0000000..b459e35 --- /dev/null +++ b/tests/srcTestRepo/README.md @@ -0,0 +1,3 @@ +# Test module + +This is a test readme. diff --git a/tests/srcTestRepo/icon/icon.png b/tests/srcTestRepo/icon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..be83fd5fd3914846c735d44415569b86c254ccad GIT binary patch literal 5882 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 OgRD;4;a-^H$^Qd{UB>JH literal 0 HcmV?d00001 diff --git a/tests/srcTestRepo/mkdocs.yml b/tests/srcTestRepo/mkdocs.yml new file mode 100644 index 0000000..df5e17a --- /dev/null +++ b/tests/srcTestRepo/mkdocs.yml @@ -0,0 +1,75 @@ +site_name: -{{ REPO_NAME }}- +theme: + name: material + language: en + font: + text: Roboto + code: Sono + logo: Assets/icon.png + favicon: Assets/icon.png + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/link + name: Switch to dark mode + # Palette toggle for dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + toggle: + primary: black + accent: green + icon: material/toggle-switch-off-outline + name: Switch to light mode + # Palette toggle for light mode + - media: '(prefers-color-scheme: light)' + scheme: default + toggle: + primary: indigo + accent: green + icon: material/toggle-switch + name: Switch to system preference + icon: + repo: material/github + features: + - navigation.instant + - navigation.instant.progress + - navigation.indexes + - navigation.top + - navigation.tracking + - navigation.expand + - search.suggest + - search.highlight + +repo_name: -{{ REPO_OWNER }}-/-{{ REPO_NAME }}- +repo_url: https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}- + +plugins: + - search + +markdown_extensions: + - toc: + permalink: true # Adds a link icon to headings + - attr_list + - admonition + - md_in_html + - pymdownx.details # Enables collapsible admonitions + +extra: + social: + - icon: fontawesome/brands/discord + link: https://discord.gg/jedJWCPAhD + name: -{{ REPO_OWNER }}- on Discord + - icon: fontawesome/brands/github + link: https://github.com/-{{ REPO_OWNER }}-/ + name: -{{ REPO_OWNER }}- on GitHub + consent: + title: Cookie consent + description: >- + We use cookies to recognize your repeated visits and preferences, as well + as to measure the effectiveness of our documentation and whether users + find what they're searching for. With your consent, you're helping us to + make our documentation better. + actions: + - accept + - reject diff --git a/tests/srcTestRepo/src/assemblies/LsonLib.dll b/tests/srcTestRepo/src/assemblies/LsonLib.dll new file mode 100644 index 0000000000000000000000000000000000000000..36618070d5c9f5131ec66720aa0565c13e86d23f GIT binary patch literal 43520 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 + + + + System.Globalization.CultureInfo + + System.Globalization.CultureInfo + + + + + 16 + + + 16 + + + + + + + + LCID + + + Name + + + DisplayName + + + + + + + + diff --git a/tests/srcTestRepo/src/formats/Mygciview.Format.ps1xml b/tests/srcTestRepo/src/formats/Mygciview.Format.ps1xml new file mode 100644 index 0000000..4c972c2 --- /dev/null +++ b/tests/srcTestRepo/src/formats/Mygciview.Format.ps1xml @@ -0,0 +1,65 @@ + + + + + mygciview + + System.IO.DirectoryInfo + System.IO.FileInfo + + + PSParentPath + + + + + + 7 + Left + + + + 26 + Right + + + + 26 + Right + + + + 14 + Right + + + + Left + + + + + + + + ModeWithoutHardLink + + + LastWriteTime + + + CreationTime + + + Length + + + Name + + + + + + + + diff --git a/tests/srcTestRepo/src/functions/private/Get-InternalPSModule.ps1 b/tests/srcTestRepo/src/functions/private/Get-InternalPSModule.ps1 new file mode 100644 index 0000000..89f053c --- /dev/null +++ b/tests/srcTestRepo/src/functions/private/Get-InternalPSModule.ps1 @@ -0,0 +1,18 @@ +function Get-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcTestRepo/src/functions/private/Set-InternalPSModule.ps1 b/tests/srcTestRepo/src/functions/private/Set-InternalPSModule.ps1 new file mode 100644 index 0000000..cf870ba --- /dev/null +++ b/tests/srcTestRepo/src/functions/private/Set-InternalPSModule.ps1 @@ -0,0 +1,22 @@ +function Set-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 b/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 new file mode 100644 index 0000000..57257f1 --- /dev/null +++ b/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 @@ -0,0 +1,23 @@ +#Requires -Modules Utilities +#Requires -Modules @{ ModuleName = 'PSSemVer'; RequiredVersion = '1.1.4' } +#Requires -Modules @{ ModuleName = 'DynamicParams'; ModuleVersion = '1.1.8' } +#Requires -Modules @{ ModuleName = 'Store'; ModuleVersion = '0.3.1' } + +function Get-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1 b/tests/srcTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1 new file mode 100644 index 0000000..5fa16bc --- /dev/null +++ b/tests/srcTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1 @@ -0,0 +1,37 @@ +#Requires -Modules @{ModuleName='PSSemVer'; ModuleVersion='1.1.4'} + +function New-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + + .NOTES + Testing if a module can have a [Markdown based link](https://example.com). + !"#¤%&/()=?`´^¨*'-_+§½{[]}<>|@£$€¥¢:;.," + \[This is a test\] + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [Alias('New-PSModuleTestAlias1')] + [Alias('New-PSModuleTestAlias2')] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} + +New-Alias New-PSModuleTestAlias3 New-PSModuleTest +New-Alias -Name New-PSModuleTestAlias4 -Value New-PSModuleTest + + +Set-Alias New-PSModuleTestAlias5 New-PSModuleTest diff --git a/tests/srcTestRepo/src/functions/public/PSModule/PSModule.md b/tests/srcTestRepo/src/functions/public/PSModule/PSModule.md new file mode 100644 index 0000000..79741cf --- /dev/null +++ b/tests/srcTestRepo/src/functions/public/PSModule/PSModule.md @@ -0,0 +1 @@ +# This is PSModule diff --git a/tests/srcTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 b/tests/srcTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 new file mode 100644 index 0000000..a87ac11 --- /dev/null +++ b/tests/srcTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 @@ -0,0 +1,22 @@ +function Set-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcTestRepo/src/functions/public/SomethingElse/SomethingElse.md b/tests/srcTestRepo/src/functions/public/SomethingElse/SomethingElse.md new file mode 100644 index 0000000..d9f7e9e --- /dev/null +++ b/tests/srcTestRepo/src/functions/public/SomethingElse/SomethingElse.md @@ -0,0 +1 @@ +# This is SomethingElse diff --git a/tests/srcTestRepo/src/functions/public/Test-PSModuleTest.ps1 b/tests/srcTestRepo/src/functions/public/Test-PSModuleTest.ps1 new file mode 100644 index 0000000..26be2b9 --- /dev/null +++ b/tests/srcTestRepo/src/functions/public/Test-PSModuleTest.ps1 @@ -0,0 +1,18 @@ +function Test-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcTestRepo/src/functions/public/completers.ps1 b/tests/srcTestRepo/src/functions/public/completers.ps1 new file mode 100644 index 0000000..6b1adbb --- /dev/null +++ b/tests/srcTestRepo/src/functions/public/completers.ps1 @@ -0,0 +1,8 @@ +Register-ArgumentCompleter -CommandName New-PSModuleTest -ParameterName Name -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters + + 'Alice', 'Bob', 'Charlie' | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} diff --git a/tests/srcTestRepo/src/header.ps1 b/tests/srcTestRepo/src/header.ps1 new file mode 100644 index 0000000..cc1fde9 --- /dev/null +++ b/tests/srcTestRepo/src/header.ps1 @@ -0,0 +1,3 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] +[CmdletBinding()] +param() diff --git a/tests/srcTestRepo/src/init/initializer.ps1 b/tests/srcTestRepo/src/init/initializer.ps1 new file mode 100644 index 0000000..28396fb --- /dev/null +++ b/tests/srcTestRepo/src/init/initializer.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------------' +Write-Verbose '--- THIS IS AN INITIALIZER ---' +Write-Verbose '-------------------------------' diff --git a/tests/srcTestRepo/src/modules/OtherPSModule.psm1 b/tests/srcTestRepo/src/modules/OtherPSModule.psm1 new file mode 100644 index 0000000..5d6af8e --- /dev/null +++ b/tests/srcTestRepo/src/modules/OtherPSModule.psm1 @@ -0,0 +1,19 @@ +function Get-OtherPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .DESCRIPTION + A longer description of the function. + + .EXAMPLE + Get-OtherPSModule -Name 'World' + #> + [CmdletBinding()] + param( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcTestRepo/src/scripts/loader.ps1 b/tests/srcTestRepo/src/scripts/loader.ps1 new file mode 100644 index 0000000..973735a --- /dev/null +++ b/tests/srcTestRepo/src/scripts/loader.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------' +Write-Verbose '--- THIS IS A LOADER ---' +Write-Verbose '-------------------------' diff --git a/tests/srcTestRepo/src/types/DirectoryInfo.Types.ps1xml b/tests/srcTestRepo/src/types/DirectoryInfo.Types.ps1xml new file mode 100644 index 0000000..aef538b --- /dev/null +++ b/tests/srcTestRepo/src/types/DirectoryInfo.Types.ps1xml @@ -0,0 +1,21 @@ + + + + System.IO.FileInfo + + + Status + Success + + + + + System.IO.DirectoryInfo + + + Status + Success + + + + diff --git a/tests/srcTestRepo/src/types/FileInfo.Types.ps1xml b/tests/srcTestRepo/src/types/FileInfo.Types.ps1xml new file mode 100644 index 0000000..4cfaf6b --- /dev/null +++ b/tests/srcTestRepo/src/types/FileInfo.Types.ps1xml @@ -0,0 +1,14 @@ + + + + System.IO.FileInfo + + + Age + + ((Get-Date) - ($this.CreationTime)).Days + + + + + diff --git a/tests/srcTestRepo/src/variables/private/PrivateVariables.ps1 b/tests/srcTestRepo/src/variables/private/PrivateVariables.ps1 new file mode 100644 index 0000000..f1fc2c3 --- /dev/null +++ b/tests/srcTestRepo/src/variables/private/PrivateVariables.ps1 @@ -0,0 +1,47 @@ +$script:HabitablePlanets = @( + @{ + Name = 'Earth' + Mass = 5.97 + Diameter = 12756 + DayLength = 24.0 + }, + @{ + Name = 'Mars' + Mass = 0.642 + Diameter = 6792 + DayLength = 24.7 + }, + @{ + Name = 'Proxima Centauri b' + Mass = 1.17 + Diameter = 11449 + DayLength = 5.15 + }, + @{ + Name = 'Kepler-442b' + Mass = 2.34 + Diameter = 11349 + DayLength = 5.7 + }, + @{ + Name = 'Kepler-452b' + Mass = 5.0 + Diameter = 17340 + DayLength = 20.0 + } +) + +$script:InhabitedPlanets = @( + @{ + Name = 'Earth' + Mass = 5.97 + Diameter = 12756 + DayLength = 24.0 + }, + @{ + Name = 'Mars' + Mass = 0.642 + Diameter = 6792 + DayLength = 24.7 + } +) diff --git a/tests/srcTestRepo/src/variables/public/Moons.ps1 b/tests/srcTestRepo/src/variables/public/Moons.ps1 new file mode 100644 index 0000000..dd0f33c --- /dev/null +++ b/tests/srcTestRepo/src/variables/public/Moons.ps1 @@ -0,0 +1,6 @@ +$script:Moons = @( + @{ + Planet = 'Earth' + Name = 'Moon' + } +) diff --git a/tests/srcTestRepo/src/variables/public/Planets.ps1 b/tests/srcTestRepo/src/variables/public/Planets.ps1 new file mode 100644 index 0000000..736584b --- /dev/null +++ b/tests/srcTestRepo/src/variables/public/Planets.ps1 @@ -0,0 +1,20 @@ +$script:Planets = @( + @{ + Name = 'Mercury' + Mass = 0.330 + Diameter = 4879 + DayLength = 4222.6 + }, + @{ + Name = 'Venus' + Mass = 4.87 + Diameter = 12104 + DayLength = 2802.0 + }, + @{ + Name = 'Earth' + Mass = 5.97 + Diameter = 12756 + DayLength = 24.0 + } +) diff --git a/tests/srcTestRepo/src/variables/public/SolarSystems.ps1 b/tests/srcTestRepo/src/variables/public/SolarSystems.ps1 new file mode 100644 index 0000000..acbcedf --- /dev/null +++ b/tests/srcTestRepo/src/variables/public/SolarSystems.ps1 @@ -0,0 +1,17 @@ +$script:SolarSystems = @( + @{ + Name = 'Solar System' + Planets = $script:Planets + Moons = $script:Moons + }, + @{ + Name = 'Alpha Centauri' + Planets = @() + Moons = @() + }, + @{ + Name = 'Sirius' + Planets = @() + Moons = @() + } +) diff --git a/tests/srcTestRepo/tests/Environment.Tests.ps1 b/tests/srcTestRepo/tests/Environment.Tests.ps1 new file mode 100644 index 0000000..211be94 --- /dev/null +++ b/tests/srcTestRepo/tests/Environment.Tests.ps1 @@ -0,0 +1,15 @@ +Describe 'Environment Variables are available' { + It 'Should be available [<_>]' -ForEach @( + 'TEST_APP_ENT_CLIENT_ID', + 'TEST_APP_ENT_PRIVATE_KEY', + 'TEST_APP_ORG_CLIENT_ID', + 'TEST_APP_ORG_PRIVATE_KEY', + 'TEST_USER_ORG_FG_PAT', + 'TEST_USER_USER_FG_PAT', + 'TEST_USER_PAT' + ) { + $name = $_ + Write-Verbose "Environment variable: [$name]" -Verbose + Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/srcTestRepo/tests/PSModuleTest.Tests.ps1 b/tests/srcTestRepo/tests/PSModuleTest.Tests.ps1 new file mode 100644 index 0000000..9bf1bb6 --- /dev/null +++ b/tests/srcTestRepo/tests/PSModuleTest.Tests.ps1 @@ -0,0 +1,44 @@ +[CmdletBinding()] +Param( + # Path to the module to test. + [Parameter()] + [string] $Path +) + +Write-Verbose "Path to the module: [$Path]" -Verbose +Describe 'PSModuleTest.Tests.ps1' { + Context 'Function: Test-PSModuleTest' { + It 'Should be able to call the function' { + Write-Verbose (Test-PSModuleTest -Name 'World' | Out-String) -Verbose + Test-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' + } + } + + Context 'Function: Get-PSModuleTest' { + It 'Should be able to call the function' { + Write-Verbose (Get-PSModuleTest -Name 'World' | Out-String) -Verbose + Get-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' + } + } + + Context 'Function: New-PSModuleTest' { + It 'Should be able to call the function' { + Write-Verbose (New-PSModuleTest -Name 'World' | Out-String) -Verbose + New-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' + } + } + + Context 'Function: Set-PSModuleTest' { + It 'Should be able to call the function' { + Write-Verbose (Set-PSModuleTest -Name 'World' | Out-String) -Verbose + Set-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' + } + } + + Context 'Variables' { + It "Exports a variable for SolarSystems that contains 'Solar System'" { + Write-Verbose ($SolarSystems | Out-String) -Verbose + $SolarSystems[0].Name | Should -Be 'Solar System' + } + } +} diff --git a/tests/srcWithManifestTestRepo/README.md b/tests/srcWithManifestTestRepo/README.md new file mode 100644 index 0000000..b459e35 --- /dev/null +++ b/tests/srcWithManifestTestRepo/README.md @@ -0,0 +1,3 @@ +# Test module + +This is a test readme. diff --git a/tests/srcWithManifestTestRepo/icon/icon.png b/tests/srcWithManifestTestRepo/icon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..be83fd5fd3914846c735d44415569b86c254ccad GIT binary patch literal 5882 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 OgRD;4;a-^H$^Qd{UB>JH literal 0 HcmV?d00001 diff --git a/tests/srcWithManifestTestRepo/mkdocs.yml b/tests/srcWithManifestTestRepo/mkdocs.yml new file mode 100644 index 0000000..df5e17a --- /dev/null +++ b/tests/srcWithManifestTestRepo/mkdocs.yml @@ -0,0 +1,75 @@ +site_name: -{{ REPO_NAME }}- +theme: + name: material + language: en + font: + text: Roboto + code: Sono + logo: Assets/icon.png + favicon: Assets/icon.png + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/link + name: Switch to dark mode + # Palette toggle for dark mode + - media: '(prefers-color-scheme: dark)' + scheme: slate + toggle: + primary: black + accent: green + icon: material/toggle-switch-off-outline + name: Switch to light mode + # Palette toggle for light mode + - media: '(prefers-color-scheme: light)' + scheme: default + toggle: + primary: indigo + accent: green + icon: material/toggle-switch + name: Switch to system preference + icon: + repo: material/github + features: + - navigation.instant + - navigation.instant.progress + - navigation.indexes + - navigation.top + - navigation.tracking + - navigation.expand + - search.suggest + - search.highlight + +repo_name: -{{ REPO_OWNER }}-/-{{ REPO_NAME }}- +repo_url: https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}- + +plugins: + - search + +markdown_extensions: + - toc: + permalink: true # Adds a link icon to headings + - attr_list + - admonition + - md_in_html + - pymdownx.details # Enables collapsible admonitions + +extra: + social: + - icon: fontawesome/brands/discord + link: https://discord.gg/jedJWCPAhD + name: -{{ REPO_OWNER }}- on Discord + - icon: fontawesome/brands/github + link: https://github.com/-{{ REPO_OWNER }}-/ + name: -{{ REPO_OWNER }}- on GitHub + consent: + title: Cookie consent + description: >- + We use cookies to recognize your repeated visits and preferences, as well + as to measure the effectiveness of our documentation and whether users + find what they're searching for. With your consent, you're helping us to + make our documentation better. + actions: + - accept + - reject diff --git a/tests/srcWithManifestTestRepo/src/assemblies/LsonLib.dll b/tests/srcWithManifestTestRepo/src/assemblies/LsonLib.dll new file mode 100644 index 0000000000000000000000000000000000000000..36618070d5c9f5131ec66720aa0565c13e86d23f GIT binary patch literal 43520 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 + + + + System.Globalization.CultureInfo + + System.Globalization.CultureInfo + + + + + 16 + + + 16 + + + + + + + + LCID + + + Name + + + DisplayName + + + + + + + + diff --git a/tests/srcWithManifestTestRepo/src/formats/Mygciview.Format.ps1xml b/tests/srcWithManifestTestRepo/src/formats/Mygciview.Format.ps1xml new file mode 100644 index 0000000..4c972c2 --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/formats/Mygciview.Format.ps1xml @@ -0,0 +1,65 @@ + + + + + mygciview + + System.IO.DirectoryInfo + System.IO.FileInfo + + + PSParentPath + + + + + + 7 + Left + + + + 26 + Right + + + + 26 + Right + + + + 14 + Right + + + + Left + + + + + + + + ModeWithoutHardLink + + + LastWriteTime + + + CreationTime + + + Length + + + Name + + + + + + + + diff --git a/tests/srcWithManifestTestRepo/src/functions/private/Get-InternalPSModule.ps1 b/tests/srcWithManifestTestRepo/src/functions/private/Get-InternalPSModule.ps1 new file mode 100644 index 0000000..89f053c --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/private/Get-InternalPSModule.ps1 @@ -0,0 +1,18 @@ +function Get-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcWithManifestTestRepo/src/functions/private/Set-InternalPSModule.ps1 b/tests/srcWithManifestTestRepo/src/functions/private/Set-InternalPSModule.ps1 new file mode 100644 index 0000000..cf870ba --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/private/Set-InternalPSModule.ps1 @@ -0,0 +1,22 @@ +function Set-InternalPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 new file mode 100644 index 0000000..57257f1 --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 @@ -0,0 +1,23 @@ +#Requires -Modules Utilities +#Requires -Modules @{ ModuleName = 'PSSemVer'; RequiredVersion = '1.1.4' } +#Requires -Modules @{ ModuleName = 'DynamicParams'; ModuleVersion = '1.1.8' } +#Requires -Modules @{ ModuleName = 'Store'; ModuleVersion = '0.3.1' } + +function Get-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcWithManifestTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1 b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1 new file mode 100644 index 0000000..5fa16bc --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/New-PSModuleTest.ps1 @@ -0,0 +1,37 @@ +#Requires -Modules @{ModuleName='PSSemVer'; ModuleVersion='1.1.4'} + +function New-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + + .NOTES + Testing if a module can have a [Markdown based link](https://example.com). + !"#¤%&/()=?`´^¨*'-_+§½{[]}<>|@£$€¥¢:;.," + \[This is a test\] + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [Alias('New-PSModuleTestAlias1')] + [Alias('New-PSModuleTestAlias2')] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} + +New-Alias New-PSModuleTestAlias3 New-PSModuleTest +New-Alias -Name New-PSModuleTestAlias4 -Value New-PSModuleTest + + +Set-Alias New-PSModuleTestAlias5 New-PSModuleTest diff --git a/tests/srcWithManifestTestRepo/src/functions/public/PSModule/PSModule.md b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/PSModule.md new file mode 100644 index 0000000..79741cf --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/PSModule.md @@ -0,0 +1 @@ +# This is PSModule diff --git a/tests/srcWithManifestTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 b/tests/srcWithManifestTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 new file mode 100644 index 0000000..a87ac11 --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/public/SomethingElse/Set-PSModuleTest.ps1 @@ -0,0 +1,22 @@ +function Set-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', + Justification = 'Reason for suppressing' + )] + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcWithManifestTestRepo/src/functions/public/SomethingElse/SomethingElse.md b/tests/srcWithManifestTestRepo/src/functions/public/SomethingElse/SomethingElse.md new file mode 100644 index 0000000..d9f7e9e --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/public/SomethingElse/SomethingElse.md @@ -0,0 +1 @@ +# This is SomethingElse diff --git a/tests/srcWithManifestTestRepo/src/functions/public/Test-PSModuleTest.ps1 b/tests/srcWithManifestTestRepo/src/functions/public/Test-PSModuleTest.ps1 new file mode 100644 index 0000000..26be2b9 --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/public/Test-PSModuleTest.ps1 @@ -0,0 +1,18 @@ +function Test-PSModuleTest { + <# + .SYNOPSIS + Performs tests on a module. + + .EXAMPLE + Test-PSModule -Name 'World' + + "Hello, World!" + #> + [CmdletBinding()] + param ( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcWithManifestTestRepo/src/functions/public/completers.ps1 b/tests/srcWithManifestTestRepo/src/functions/public/completers.ps1 new file mode 100644 index 0000000..6b1adbb --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/functions/public/completers.ps1 @@ -0,0 +1,8 @@ +Register-ArgumentCompleter -CommandName New-PSModuleTest -ParameterName Name -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters + + 'Alice', 'Bob', 'Charlie' | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} diff --git a/tests/srcWithManifestTestRepo/src/header.ps1 b/tests/srcWithManifestTestRepo/src/header.ps1 new file mode 100644 index 0000000..cc1fde9 --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/header.ps1 @@ -0,0 +1,3 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] +[CmdletBinding()] +param() diff --git a/tests/srcWithManifestTestRepo/src/init/initializer.ps1 b/tests/srcWithManifestTestRepo/src/init/initializer.ps1 new file mode 100644 index 0000000..28396fb --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/init/initializer.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------------' +Write-Verbose '--- THIS IS AN INITIALIZER ---' +Write-Verbose '-------------------------------' diff --git a/tests/srcWithManifestTestRepo/src/manifest.psd1 b/tests/srcWithManifestTestRepo/src/manifest.psd1 new file mode 100644 index 0000000..77348f1 --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/manifest.psd1 @@ -0,0 +1,3 @@ +@{ + Author = 'Author' +} diff --git a/tests/srcWithManifestTestRepo/src/modules/OtherPSModule.psm1 b/tests/srcWithManifestTestRepo/src/modules/OtherPSModule.psm1 new file mode 100644 index 0000000..5d6af8e --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/modules/OtherPSModule.psm1 @@ -0,0 +1,19 @@ +function Get-OtherPSModule { + <# + .SYNOPSIS + Performs tests on a module. + + .DESCRIPTION + A longer description of the function. + + .EXAMPLE + Get-OtherPSModule -Name 'World' + #> + [CmdletBinding()] + param( + # Name of the person to greet. + [Parameter(Mandatory)] + [string] $Name + ) + Write-Output "Hello, $Name!" +} diff --git a/tests/srcWithManifestTestRepo/src/scripts/loader.ps1 b/tests/srcWithManifestTestRepo/src/scripts/loader.ps1 new file mode 100644 index 0000000..973735a --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/scripts/loader.ps1 @@ -0,0 +1,3 @@ +Write-Verbose '-------------------------' +Write-Verbose '--- THIS IS A LOADER ---' +Write-Verbose '-------------------------' diff --git a/tests/srcWithManifestTestRepo/src/types/DirectoryInfo.Types.ps1xml b/tests/srcWithManifestTestRepo/src/types/DirectoryInfo.Types.ps1xml new file mode 100644 index 0000000..aef538b --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/types/DirectoryInfo.Types.ps1xml @@ -0,0 +1,21 @@ + + + + System.IO.FileInfo + + + Status + Success + + + + + System.IO.DirectoryInfo + + + Status + Success + + + + diff --git a/tests/srcWithManifestTestRepo/src/types/FileInfo.Types.ps1xml b/tests/srcWithManifestTestRepo/src/types/FileInfo.Types.ps1xml new file mode 100644 index 0000000..4cfaf6b --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/types/FileInfo.Types.ps1xml @@ -0,0 +1,14 @@ + + + + System.IO.FileInfo + + + Age + + ((Get-Date) - ($this.CreationTime)).Days + + + + + diff --git a/tests/srcWithManifestTestRepo/src/variables/private/PrivateVariables.ps1 b/tests/srcWithManifestTestRepo/src/variables/private/PrivateVariables.ps1 new file mode 100644 index 0000000..f1fc2c3 --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/variables/private/PrivateVariables.ps1 @@ -0,0 +1,47 @@ +$script:HabitablePlanets = @( + @{ + Name = 'Earth' + Mass = 5.97 + Diameter = 12756 + DayLength = 24.0 + }, + @{ + Name = 'Mars' + Mass = 0.642 + Diameter = 6792 + DayLength = 24.7 + }, + @{ + Name = 'Proxima Centauri b' + Mass = 1.17 + Diameter = 11449 + DayLength = 5.15 + }, + @{ + Name = 'Kepler-442b' + Mass = 2.34 + Diameter = 11349 + DayLength = 5.7 + }, + @{ + Name = 'Kepler-452b' + Mass = 5.0 + Diameter = 17340 + DayLength = 20.0 + } +) + +$script:InhabitedPlanets = @( + @{ + Name = 'Earth' + Mass = 5.97 + Diameter = 12756 + DayLength = 24.0 + }, + @{ + Name = 'Mars' + Mass = 0.642 + Diameter = 6792 + DayLength = 24.7 + } +) diff --git a/tests/srcWithManifestTestRepo/src/variables/public/Moons.ps1 b/tests/srcWithManifestTestRepo/src/variables/public/Moons.ps1 new file mode 100644 index 0000000..dd0f33c --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/variables/public/Moons.ps1 @@ -0,0 +1,6 @@ +$script:Moons = @( + @{ + Planet = 'Earth' + Name = 'Moon' + } +) diff --git a/tests/srcWithManifestTestRepo/src/variables/public/Planets.ps1 b/tests/srcWithManifestTestRepo/src/variables/public/Planets.ps1 new file mode 100644 index 0000000..736584b --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/variables/public/Planets.ps1 @@ -0,0 +1,20 @@ +$script:Planets = @( + @{ + Name = 'Mercury' + Mass = 0.330 + Diameter = 4879 + DayLength = 4222.6 + }, + @{ + Name = 'Venus' + Mass = 4.87 + Diameter = 12104 + DayLength = 2802.0 + }, + @{ + Name = 'Earth' + Mass = 5.97 + Diameter = 12756 + DayLength = 24.0 + } +) diff --git a/tests/srcWithManifestTestRepo/src/variables/public/SolarSystems.ps1 b/tests/srcWithManifestTestRepo/src/variables/public/SolarSystems.ps1 new file mode 100644 index 0000000..acbcedf --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/variables/public/SolarSystems.ps1 @@ -0,0 +1,17 @@ +$script:SolarSystems = @( + @{ + Name = 'Solar System' + Planets = $script:Planets + Moons = $script:Moons + }, + @{ + Name = 'Alpha Centauri' + Planets = @() + Moons = @() + }, + @{ + Name = 'Sirius' + Planets = @() + Moons = @() + } +) diff --git a/tests/srcWithManifestTestRepo/tests/Environments/Environment.Tests.ps1 b/tests/srcWithManifestTestRepo/tests/Environments/Environment.Tests.ps1 new file mode 100644 index 0000000..211be94 --- /dev/null +++ b/tests/srcWithManifestTestRepo/tests/Environments/Environment.Tests.ps1 @@ -0,0 +1,15 @@ +Describe 'Environment Variables are available' { + It 'Should be available [<_>]' -ForEach @( + 'TEST_APP_ENT_CLIENT_ID', + 'TEST_APP_ENT_PRIVATE_KEY', + 'TEST_APP_ORG_CLIENT_ID', + 'TEST_APP_ORG_PRIVATE_KEY', + 'TEST_USER_ORG_FG_PAT', + 'TEST_USER_USER_FG_PAT', + 'TEST_USER_PAT' + ) { + $name = $_ + Write-Verbose "Environment variable: [$name]" -Verbose + Get-ChildItem env: | Where-Object { $_.Name -eq $name } | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/srcWithManifestTestRepo/tests/MyTests/PSModuleTest.Tests.ps1 b/tests/srcWithManifestTestRepo/tests/MyTests/PSModuleTest.Tests.ps1 new file mode 100644 index 0000000..9bf1bb6 --- /dev/null +++ b/tests/srcWithManifestTestRepo/tests/MyTests/PSModuleTest.Tests.ps1 @@ -0,0 +1,44 @@ +[CmdletBinding()] +Param( + # Path to the module to test. + [Parameter()] + [string] $Path +) + +Write-Verbose "Path to the module: [$Path]" -Verbose +Describe 'PSModuleTest.Tests.ps1' { + Context 'Function: Test-PSModuleTest' { + It 'Should be able to call the function' { + Write-Verbose (Test-PSModuleTest -Name 'World' | Out-String) -Verbose + Test-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' + } + } + + Context 'Function: Get-PSModuleTest' { + It 'Should be able to call the function' { + Write-Verbose (Get-PSModuleTest -Name 'World' | Out-String) -Verbose + Get-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' + } + } + + Context 'Function: New-PSModuleTest' { + It 'Should be able to call the function' { + Write-Verbose (New-PSModuleTest -Name 'World' | Out-String) -Verbose + New-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' + } + } + + Context 'Function: Set-PSModuleTest' { + It 'Should be able to call the function' { + Write-Verbose (Set-PSModuleTest -Name 'World' | Out-String) -Verbose + Set-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' + } + } + + Context 'Variables' { + It "Exports a variable for SolarSystems that contains 'Solar System'" { + Write-Verbose ($SolarSystems | Out-String) -Verbose + $SolarSystems[0].Name | Should -Be 'Solar System' + } + } +} diff --git a/tests/srcWithManifestTestRepo/tools/1-build.ps1 b/tests/srcWithManifestTestRepo/tools/1-build.ps1 new file mode 100644 index 0000000..e762395 --- /dev/null +++ b/tests/srcWithManifestTestRepo/tools/1-build.ps1 @@ -0,0 +1 @@ +"1 - Build script executed." diff --git a/tests/srcWithManifestTestRepo/tools/2-build.ps1 b/tests/srcWithManifestTestRepo/tools/2-build.ps1 new file mode 100644 index 0000000..d2575a0 --- /dev/null +++ b/tests/srcWithManifestTestRepo/tools/2-build.ps1 @@ -0,0 +1 @@ +"2 - Build script executed." From 8b654d62d9f255486081abedfd3ca69c2795a5e8 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 11:56:53 +0100 Subject: [PATCH 02/14] Refactor GitHub Actions workflows to remove unnecessary parameters and streamline test configurations --- .github/workflows/Action-Test-Src-Default.yml | 2 -- .github/workflows/Action-Test-Src-WithManifest.yml | 2 -- .github/workflows/Action-Test-outputs.yml | 4 +--- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/Action-Test-Src-Default.yml b/.github/workflows/Action-Test-Src-Default.yml index c48f749..7d03650 100644 --- a/.github/workflows/Action-Test-Src-Default.yml +++ b/.github/workflows/Action-Test-Src-Default.yml @@ -35,10 +35,8 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} with: - Name: PSModuleTest Path: tests/srcTestRepo TestType: SourceCode - OS: ${{ matrix.os }} - name: Status shell: pwsh diff --git a/.github/workflows/Action-Test-Src-WithManifest.yml b/.github/workflows/Action-Test-Src-WithManifest.yml index 3ac93cc..bde3bd4 100644 --- a/.github/workflows/Action-Test-Src-WithManifest.yml +++ b/.github/workflows/Action-Test-Src-WithManifest.yml @@ -35,10 +35,8 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} with: - Name: PSModuleTest Path: tests/srcWithManifestTestRepo Test: SourceCode - OS: ${{ matrix.os }} - name: Status shell: pwsh diff --git a/.github/workflows/Action-Test-outputs.yml b/.github/workflows/Action-Test-outputs.yml index 0406a4c..5fedc62 100644 --- a/.github/workflows/Action-Test-outputs.yml +++ b/.github/workflows/Action-Test-outputs.yml @@ -35,10 +35,8 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} with: - Name: PSModuleTest Path: tests/outputTestRepo - TestType: Module - OS: ${{ matrix.os }} + Settings: Module - name: Status shell: pwsh From 262b5e71d977bcb27771ee0ffa46d6f68de86f81 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:02:06 +0100 Subject: [PATCH 03/14] Update test path in main.ps1 to point to the correct directory for PSScriptAnalyzer --- scripts/main.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 4b78ea1..b864f30 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -1,6 +1,6 @@ # If test type is module, the code we ought to test is in the path/name folder, otherwise it's in the path folder. $settings = $env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Settings -$testPath = Resolve-Path -Path "$PSScriptRoot/PSScriptAnalyzer" | Select-Object -ExpandProperty Path +$testPath = Resolve-Path -Path "$PSScriptRoot/tests/PSScriptAnalyzer" | Select-Object -ExpandProperty Path $codePath = Resolve-Path -Path $env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Path | Select-Object -ExpandProperty Path $settingsPath = switch -Regex ($settings) { 'Module|SourceCode' { From dd476f764575d37c748a3b356f90d8ee1132b042 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:10:44 +0100 Subject: [PATCH 04/14] Rename SettingsPath to SettingsFilePath in action.yml and update references in main.ps1 and PSScriptAnalyzer.Container.ps1 --- action.yml | 9 ++++++--- scripts/main.ps1 | 14 +++++++------- .../PSScriptAnalyzer.Container.ps1 | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/action.yml b/action.yml index b5c2f66..86c9789 100644 --- a/action.yml +++ b/action.yml @@ -14,7 +14,7 @@ inputs: description: The type of tests to run. Can be either 'Module', 'SourceCode' or 'Custom'. required: false default: 'Custom' - SettingsPath: + SettingsFilePath: description: The path to the settings file. required: false default: ${{ github.workspace }}/.github/linters/.powershell-psscriptanalyzer.psd1 @@ -33,14 +33,17 @@ runs: env: GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Path: ${{ inputs.Path }} GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Settings: ${{ inputs.Settings }} - GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_SettingsPath: ${{ inputs.SettingsPath }} + GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_SettingsFilePath: ${{ inputs.SettingsFilePath }} with: Script: ${{ github.action_path }}/scripts/main.ps1 - name: Invoke-Pester uses: PSModule/Invoke-Pester@fix id: test + env: + Settings: ${{ fromJson(steps.paths.outputs.result).Settings }} + SettingsFilePath: ${{ fromJson(steps.paths.outputs.result).SettingsFilePath }} with: TestResult_TestSuiteName: PSScriptAnalyzer - Path: ${{ fromJson(steps.paths.outputs.result).TestPath }} + Path: ${{ github.action_path }}/tests/PSScriptAnalyzer Run_Path: ${{ inputs.Path }} diff --git a/scripts/main.ps1 b/scripts/main.ps1 index b864f30..b2822d8 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -2,12 +2,12 @@ $settings = $env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Settings $testPath = Resolve-Path -Path "$PSScriptRoot/tests/PSScriptAnalyzer" | Select-Object -ExpandProperty Path $codePath = Resolve-Path -Path $env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_Path | Select-Object -ExpandProperty Path -$settingsPath = switch -Regex ($settings) { +$settingsFilePath = switch -Regex ($settings) { 'Module|SourceCode' { "$testPath/$settings.Settings.psd1" } 'Custom' { - Resolve-Path -Path "$env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_SettingsPath" | Select-Object -ExpandProperty Path + Resolve-Path -Path "$env:GITHUB_ACTION_INVOKE_SCRIPTANALYZER_INPUT_SettingsFilePath" | Select-Object -ExpandProperty Path } default { throw "Invalid test type: [$settings]" @@ -15,13 +15,13 @@ $settingsPath = switch -Regex ($settings) { } [pscustomobject]@{ - Settings = $settings - CodePath = $codePath - TestPath = $testPath - SettingsPath = $settingsPath + Settings = $settings + CodePath = $codePath + TestPath = $testPath + SettingsFilePath = $settingsFilePath } | Format-List Set-GitHubOutput -Name Settings -Value $settings Set-GitHubOutput -Name CodePath -Value $codePath Set-GitHubOutput -Name TestPath -Value $testPath -Set-GitHubOutput -Name SettingsPath -Value $settingsPath +Set-GitHubOutput -Name SettingsFilePath -Value $settingsFilePath diff --git a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Container.ps1 b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Container.ps1 index 7d1e654..404a041 100644 --- a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Container.ps1 +++ b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Container.ps1 @@ -1,8 +1,8 @@ @{ Path = Get-ChildItem -Path $PSScriptRoot -Filter *.Tests.ps1 | Select-Object -ExpandProperty FullName Data = @{ - Path = "$env:GITHUB_ACTION_INPUT_Run_Path/output/modules/$env:ModuleName" - SettingsFilePath = "$PSScriptRoot/PSScriptAnalyzer.Settings.psd1" + Path = $env:GITHUB_ACTION_INPUT_Run_Path + SettingsFilePath = $env:SettingsFilePath Debug = $false Verbose = $false } From 6c1c345b582ed0319c832617d786b6307633695a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:13:31 +0100 Subject: [PATCH 05/14] Update workflow configurations to use 'Settings' instead of 'TestType' and adjust suppress messages in PSScriptAnalyzer tests --- .github/workflows/Action-Test-Src-Default.yml | 2 +- .github/workflows/Action-Test-Src-WithManifest.yml | 2 +- .../PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 | 12 ++++-------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/Action-Test-Src-Default.yml b/.github/workflows/Action-Test-Src-Default.yml index 7d03650..86c785e 100644 --- a/.github/workflows/Action-Test-Src-Default.yml +++ b/.github/workflows/Action-Test-Src-Default.yml @@ -36,7 +36,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} with: Path: tests/srcTestRepo - TestType: SourceCode + Settings: SourceCode - name: Status shell: pwsh diff --git a/.github/workflows/Action-Test-Src-WithManifest.yml b/.github/workflows/Action-Test-Src-WithManifest.yml index bde3bd4..642134b 100644 --- a/.github/workflows/Action-Test-Src-WithManifest.yml +++ b/.github/workflows/Action-Test-Src-WithManifest.yml @@ -36,7 +36,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} with: Path: tests/srcWithManifestTestRepo - Test: SourceCode + Settings: SourceCode - name: Status shell: pwsh diff --git a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 index 6fb251e..6998e75 100644 --- a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 +++ b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 @@ -1,14 +1,10 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSReviewUnusedParameter', 'Path', - Justification = 'Path is being used.' + 'PSReviewUnusedParameter', '', + Justification = 'Pester blocks line of sight during analysis.' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSReviewUnusedParameter', 'SettingsFilePath', - Justification = 'SettingsFilePath is being used.' -)] -[Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSUseDeclaredVarsMoreThanAssignments', 'relativeSettingsFilePath', - Justification = 'relativeSettingsFilePath is being used.' + 'PSUseDeclaredVarsMoreThanAssignments', '', + Justification = 'Pester blocks line of sight during analysis.' )] [CmdLetBinding()] Param( From 2bc0e8c0303da2780852c28ea8608508c624fd8e Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:15:08 +0100 Subject: [PATCH 06/14] Update test path in action.yml to point to the correct directory for PSScriptAnalyzer --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 86c9789..03c4eeb 100644 --- a/action.yml +++ b/action.yml @@ -45,5 +45,5 @@ runs: SettingsFilePath: ${{ fromJson(steps.paths.outputs.result).SettingsFilePath }} with: TestResult_TestSuiteName: PSScriptAnalyzer - Path: ${{ github.action_path }}/tests/PSScriptAnalyzer + Path: ${{ github.action_path }}/scripts/tests/PSScriptAnalyzer Run_Path: ${{ inputs.Path }} From 4f950965724170defc03184af1aefdf76a81cafb Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:21:10 +0100 Subject: [PATCH 07/14] Update test paths in workflow files to point to the correct subdirectories --- .github/workflows/Action-Test-Src-Default.yml | 2 +- .github/workflows/Action-Test-Src-WithManifest.yml | 2 +- .github/workflows/Action-Test-outputs.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Action-Test-Src-Default.yml b/.github/workflows/Action-Test-Src-Default.yml index 86c785e..d6478a0 100644 --- a/.github/workflows/Action-Test-Src-Default.yml +++ b/.github/workflows/Action-Test-Src-Default.yml @@ -35,7 +35,7 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} with: - Path: tests/srcTestRepo + Path: tests/srcTestRepo/src Settings: SourceCode - name: Status diff --git a/.github/workflows/Action-Test-Src-WithManifest.yml b/.github/workflows/Action-Test-Src-WithManifest.yml index 642134b..700d39b 100644 --- a/.github/workflows/Action-Test-Src-WithManifest.yml +++ b/.github/workflows/Action-Test-Src-WithManifest.yml @@ -35,7 +35,7 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} with: - Path: tests/srcWithManifestTestRepo + Path: tests/srcWithManifestTestRepo/src Settings: SourceCode - name: Status diff --git a/.github/workflows/Action-Test-outputs.yml b/.github/workflows/Action-Test-outputs.yml index 5fedc62..9c56814 100644 --- a/.github/workflows/Action-Test-outputs.yml +++ b/.github/workflows/Action-Test-outputs.yml @@ -35,7 +35,7 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} with: - Path: tests/outputTestRepo + Path: tests/outputTestRepo/outputs/modules/PSModuleTest Settings: Module - name: Status From be1a57d6274fc39b02e1a066a7d4a88a555af044 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:34:02 +0100 Subject: [PATCH 08/14] Refactor workflow files to use a centralized ActionTestWorkflow and streamline job configurations --- .github/workflows/Action-Test-Src-Default.yml | 36 ++---------- .../Action-Test-Src-WithManifest.yml | 36 ++---------- .github/workflows/Action-Test-outputs.yml | 37 ++---------- .github/workflows/ActionTestWorkflow.yml | 58 +++++++++++++++++++ 4 files changed, 74 insertions(+), 93 deletions(-) create mode 100644 .github/workflows/ActionTestWorkflow.yml diff --git a/.github/workflows/Action-Test-Src-Default.yml b/.github/workflows/Action-Test-Src-Default.yml index d6478a0..7896cc0 100644 --- a/.github/workflows/Action-Test-Src-Default.yml +++ b/.github/workflows/Action-Test-Src-Default.yml @@ -16,34 +16,8 @@ permissions: {} jobs: ActionTest: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - name: Action-Test [Src-Default] - [${{ matrix.os }}] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@main - - - name: Action-Test - uses: ./ - id: action-test - env: - GITHUB_TOKEN: ${{ github.token }} - with: - Path: tests/srcTestRepo/src - Settings: SourceCode - - - name: Status - shell: pwsh - env: - PASSED: ${{ steps.action-test.outputs.passed }} - run: | - Write-Host "Passed: [$env:PASSED]" - if ($env:PASSED -ne 'true') { - exit 1 - } + uses: ./.github/workflows/ActionTestWorkflow.yml + with: + TestType: Src-Default + Path: tests/srcTestRepo/src + Settings: SourceCode diff --git a/.github/workflows/Action-Test-Src-WithManifest.yml b/.github/workflows/Action-Test-Src-WithManifest.yml index 700d39b..f2fb1ff 100644 --- a/.github/workflows/Action-Test-Src-WithManifest.yml +++ b/.github/workflows/Action-Test-Src-WithManifest.yml @@ -16,34 +16,8 @@ permissions: {} jobs: ActionTest: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - name: Action-Test [Src-WithManifest] - [${{ matrix.os }}] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@main - - - name: Action-Test - uses: ./ - id: action-test - env: - GITHUB_TOKEN: ${{ github.token }} - with: - Path: tests/srcWithManifestTestRepo/src - Settings: SourceCode - - - name: Status - shell: pwsh - env: - PASSED: ${{ steps.action-test.outputs.passed }} - run: | - Write-Host "Passed: [$env:PASSED]" - if ($env:PASSED -ne 'true') { - exit 1 - } + uses: ./.github/workflows/ActionTestWorkflow.yml + with: + TestType: Src-WithManifest + Path: tests/srcWithManifestTestRepo/src + Settings: SourceCode diff --git a/.github/workflows/Action-Test-outputs.yml b/.github/workflows/Action-Test-outputs.yml index 9c56814..c318e4c 100644 --- a/.github/workflows/Action-Test-outputs.yml +++ b/.github/workflows/Action-Test-outputs.yml @@ -16,34 +16,9 @@ permissions: {} jobs: ActionTest: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - name: Action-Test [outputs] - [${{ matrix.os }}] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Initialize environment - uses: PSModule/Initialize-PSModule@main - - - name: Action-Test - uses: ./ - id: action-test - env: - GITHUB_TOKEN: ${{ github.token }} - with: - Path: tests/outputTestRepo/outputs/modules/PSModuleTest - Settings: Module - - - name: Status - shell: pwsh - env: - PASSED: ${{ steps.action-test.outputs.passed }} - run: | - Write-Host "Passed: [$env:PASSED]" - if ($env:PASSED -ne 'true') { - exit 1 - } + uses: ./.github/workflows/ActionTestWorkflow.yml + with: + TestType: outputs + Path: tests/outputTestRepo/outputs/modules/PSModuleTest + Settings: Module + SettingsFilePath: diff --git a/.github/workflows/ActionTestWorkflow.yml b/.github/workflows/ActionTestWorkflow.yml new file mode 100644 index 0000000..0297405 --- /dev/null +++ b/.github/workflows/ActionTestWorkflow.yml @@ -0,0 +1,58 @@ +name: Action-Test [outputs] + +run-name: "Action-Test [outputs] - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" + +on: + workflow_call: + inputs: + TestType: + type: string + required: true + Path: + type: string + required: true + Settings: + type: string + required: false + SettingsFilePath: + type: string + required: false + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + ActionTest: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + name: Action-Test [outputs] - [${{ matrix.os }}] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Initialize environment + uses: PSModule/Initialize-PSModule@main + + - name: Action-Test + uses: ./ + id: action-test + with: + Path: ${{ inputs.Path }} + Settings: ${{ inputs.Settings }} + SettingsFilePath: ${{ inputs.SettingsFilePath }} + + - name: Status + shell: pwsh + env: + PASSED: ${{ steps.action-test.outputs.passed }} + run: | + Write-Host "Passed: [$env:PASSED]" + if ($env:PASSED -ne 'true') { + exit 1 + } From 3a9ad089336dee9760df73dda82e60501e91f11c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:37:59 +0100 Subject: [PATCH 09/14] Remove unnecessary metadata and concurrency settings from ActionTestWorkflow.yml --- .github/workflows/ActionTestWorkflow.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/ActionTestWorkflow.yml b/.github/workflows/ActionTestWorkflow.yml index 0297405..b8f12df 100644 --- a/.github/workflows/ActionTestWorkflow.yml +++ b/.github/workflows/ActionTestWorkflow.yml @@ -1,7 +1,3 @@ -name: Action-Test [outputs] - -run-name: "Action-Test [outputs] - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}" - on: workflow_call: inputs: @@ -18,10 +14,6 @@ on: type: string required: false -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - permissions: {} jobs: From 25957e99cd84abe80bf7b6286f0fb7f6447a2325 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:48:51 +0100 Subject: [PATCH 10/14] Update PSScriptAnalyzer settings and enhance rule documentation --- About_Rules.md | 391 ------------------ Rules.md | 116 ++++++ .../PSScriptAnalyzer.Tests.ps1 | 15 +- 3 files changed, 124 insertions(+), 398 deletions(-) delete mode 100644 About_Rules.md create mode 100644 Rules.md diff --git a/About_Rules.md b/About_Rules.md deleted file mode 100644 index b66ca58..0000000 --- a/About_Rules.md +++ /dev/null @@ -1,391 +0,0 @@ -# Using a Hashtable-Based Settings File for PSScriptAnalyzer in PowerShell 7 - -## Introduction - -**PSScriptAnalyzer** is a static code checker for PowerShell modules and scripts. It evaluates your code against a set of built-in rules based on PowerShell best practices identified by the PowerShell team and community ([PSScriptAnalyzer/README.md at main · PowerShell/PSScriptAnalyzer · GitHub](https://github.com/PowerShell/PSScriptAnalyzer/blob/master/README.md#:~:text=PSScriptAnalyzer%20is%20a%20static%20code,suggests%20possible%20solutions%20for%20improvements)). When run (for example, via the `Invoke-ScriptAnalyzer` cmdlet), it produces diagnostics (errors, warnings, or informational messages) to highlight potential issues and suggest improvements. This helps maintain code quality by catching common mistakes, stylistic issues, or potential bugs early in the development process. - -Using a **settings file** for PSScriptAnalyzer allows you to customize which rules are applied and how they're reported, without having to specify numerous parameters on each run. In effect, a settings file acts as a profile or configuration that PSScriptAnalyzer will follow, much like *splatting* parameters in a single hashtable ([Invoke-ScriptAnalyzer (PSScriptAnalyzer) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules#:~:text=The%20keys%20and%20values%20in,For%20more%20information%2C%20see%20about_Splatting)). This approach is beneficial for several reasons: - -- **Consistency**: By sharing a common settings file across your project or team, you ensure that everyone’s code is analyzed with the same rules and standards. This avoids “it works on my machine” discrepancies in linting results. -- **Customization**: Not all projects are the same. A settings file allows you to **include or exclude specific rules** and even filter by severity levels to tailor the analysis to your project's needs ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=The%20following%20example%20excludes%20two,other%20than%20Error%20and%20Warning)). For instance, you might choose to treat only errors and warnings as relevant, ignoring info-level suggestions in certain CI scenarios. -- **Maintainability**: It's easier to update one configuration file than to modify multiple build or script invocations. If you decide to enable a new rule or adjust severities, you can do it in one place. The settings file “does everything the different parameters on `Invoke-ScriptAnalyzer` [would do]” ([Creating custom PSScriptAnalyzer rules](https://blog.ironmansoftware.com/psscriptanalyzer-custom-rules/#:~:text=In%20order%20to%20customize%20,ScriptAnalyzer)), so it centralizes your static analysis configuration. -- **Integration**: Many tools (like VS Code, CI/CD pipelines, and GitHub actions) can automatically pick up your PSScriptAnalyzer settings file. This implicit usage means you often just drop the file in the right location and your rules preferences will be applied without extra scripting ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). - -In summary, a hashtable-based settings file gives you fine-grained control over PSScriptAnalyzer’s behavior. In the latest PowerShell (Core 7+) environment, PSScriptAnalyzer fully supports such configuration files, making it easier to enforce coding standards and best practices consistently across various contexts. - -## Basic Setup - -This section guides you through creating a PSScriptAnalyzer settings file (which is a PowerShell **.psd1 data file**) and getting it ready for use. We will assume a common scenario of using a GitHub repository, with the settings file stored at `.github/linters/.powershell-psscriptanalyzer.psd1` (a location conventionally used by some CI linters), but you can adapt the location to your needs. The file itself can be named anything, but by default PSScriptAnalyzer looks for **`PSScriptAnalyzerSettings.psd1`** if no explicit path is provided ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). - -**Steps to create the settings file:** - -1. **Choose a Location:** Decide where to place the settings file. For automatic discovery, the recommended name is `PSScriptAnalyzerSettings.psd1` in the root of your project (so that if you run PSScriptAnalyzer on the project folder, it finds the file) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). If you are using GitHub's Super-Linter Action or similar, the convention is to put the file under a `.github/linters/` directory with a language-specific name. For example, GitHub Super-Linter will look for a PowerShell analyzer config at `.github/linters/.powershell-psscriptanalyzer.psd1` by default ([ - PowerShell Gallery - | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview - ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=Documentation%3A%20https%3A%2F%2Fgithub.com%2FPowerShell%2FPSScriptAnalyzer%2Fblob%2Fmaster%2Fdocs%2Fmarkdown%2FInvoke,RecurseCustomRulePath%3D%27path%5Cof%5Ccustomrules%27%20Severity%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3D)). Create the directories if they don't exist: - ```bash - mkdir -p .github/linters - ``` - Then create an empty file named `.powershell-psscriptanalyzer.psd1` in that folder. - -2. **File Structure:** Open the file in a text editor. A PSScriptAnalyzer settings file is essentially a PowerShell hashtable literal enclosed in `@{ ... }`. Start with an empty hashtable: - ```powershell - @{} - ``` - This empty config would mean "use all default rules with default severities." It’s a valid starting point, but typically you will add keys and values to customize the analysis. Each key in this hashtable corresponds to a PSScriptAnalyzer parameter or setting (like which rules to include/exclude). We will cover these keys in the next sections. - -3. **Add Basic Configuration:** As a quick test, you might add a simple setting. For example, to only show errors (and hide warnings/information messages), you could set the **Severity** filter in the file: - ```powershell - @{ - Severity = @('Error') - } - ``` - This is a minimal configuration that tells PSScriptAnalyzer to report only rule violations of severity "Error" ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=%40%7B%20,Error%27%29)). For now, you can save the file with just this content or another simple tweak. We will expand on the various settings in subsequent sections. - -4. **Using the Settings File:** To verify that your settings file is recognized, run PSScriptAnalyzer with it. From a PowerShell prompt at the root of your project, execute: - ```powershell - Invoke-ScriptAnalyzer -Path . -Recurse -Settings .\.github\linters\.powershell-psscriptanalyzer.psd1 - ``` - Replace the `-Path` with the path to your scripts (here we use `.` for current directory, and `-Recurse` to analyze all subfolders). The `-Settings` parameter points to the file we created. PSScriptAnalyzer will load the hashtable from that file and apply those settings for the analysis run. You should see that the output now reflects your configuration (e.g., only errors if you set `Severity='Error'` earlier). If you placed a file named `PSScriptAnalyzerSettings.psd1` at the root of the path you're analyzing, you could even omit the `-Settings` parameter and PSScriptAnalyzer would **implicitly** find it ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). - -5. **Commit the File (if applicable):** If this is for a repository (especially with CI integration), be sure to add and commit the new `.psd1` file to version control. This allows the settings to travel with the code, so that other developers and automated pipelines use the same analyzer configuration. - -At this point, you have a basic settings file set up. Next, we'll dive into how to configure specific rules and options within that file to fine-tune the analyzer to your needs. - -## Configuring Rules - -The power of a PSScriptAnalyzer settings file comes from the ability to **enable or disable specific rules** and **adjust filtering options like severity** in one place. The settings file uses certain predefined keys (entries in the hashtable) that PSScriptAnalyzer recognizes. These keys correspond to parameters you might otherwise pass to `Invoke-ScriptAnalyzer`. The most commonly used keys include: - -- **`IncludeRules`** – A list of rule names to **include** (run) during analysis ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=,)). Only rules in this list will be executed (all others will be skipped). Wildcards are supported (e.g., `'PSAvoid*'` to include all rules starting with "PSAvoid"). -- **`ExcludeRules`** – A list of rule names to **exclude** from analysis ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=,)). All rules except those listed will run. Use this when you want most rules enabled, but need to turn off a few that aren't relevant or cause noise. Wildcards can be used here as well. -- **`Severity`** – A list of severity levels to report. By default, PSScriptAnalyzer rules can output findings of severity **Error**, **Warning**, or **Information**. Using this setting filters out findings that are not in the list. For example, `Severity = @('Error','Warning')` means informational messages will be omitted from results ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Severity%3D%40%28%27Error%27%2C%27Warning%27%29%20ExcludeRules%3D%40%28%27PSAvoidUsingCmdletAliases%27%2C%20%27PSAvoidUsingWriteHost%27%29%20)). This does **not** change the inherent severity of rules; it just acts as a post-analysis filter for what gets reported ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=The%20following%20example%20excludes%20two,other%20than%20Error%20and%20Warning)). (Note: Currently, you cannot directly change a rule's designated severity via the settings file – you can only filter what severities to see.) -- **`IncludeDefaultRules`** – A Boolean switch ( `$true` / `$false` ) that determines whether the built-in default rules should be included. This is typically used in combination with custom rules (discussed later). For instance, if you are using custom rules and want to **also** run the normal PSScriptAnalyzer rules, set `IncludeDefaultRules = $true` ([ - PowerShell Gallery - | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview - ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=Severity%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3D%C2%A0%40%28%20%27Error%27%20%27Warning%27%20%29%20IncludeDefaultRules%C2%A0%3D%C2%A0%24,%27PSUseShouldProcessForStateChangingFunctions%27%2C%20%27PSAvoidUsingConvertToSecureStringWithPlainText)). If false (or not set, which defaults to false when custom rules are specified), you might be running only a custom rule set. In normal use (without custom rules), you don't need to set this – by default all built-in rules run unless you exclude or limit them. -- **`CustomRulePath`** – One or more filesystem paths to custom rule scripts or modules. Custom rules allow you to extend PSScriptAnalyzer with your own rules; by specifying their path here, PSSA will load and include them in analysis ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=In%20this%20example%20the%20property,used%20for%20the%20property%20IncludeRules)). (More on custom rules in its own section below.) -- **`RecurseCustomRulePath`** – A Boolean indicating if PSScriptAnalyzer should search subdirectories of the provided custom rule path(s) for additional rule files ([ - PowerShell Gallery - | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview - ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=psscriptanalyzer.psd1%20%40%7B%20,true)). Set this to `$true` if you've organized custom rules in a folder hierarchy and want all of them picked up. If you enable this, and you *only* want custom rules, remember to disable default rules (or don't set IncludeDefaultRules) to avoid running everything. -- **`Rules`** – A nested hashtable for providing **per-rule specific settings**. This is used to pass **rule parameters** to particular rules, or to override certain rule behaviors. For example, some rules have optional parameters (like a whitelist of allowed terms or specific options). If you want to provide those, you do so under `Rules`. The key is the rule name, and the value is another hashtable of that rule's parameter names and values. For example: - ```powershell - Rules = @{ - PSAvoidUsingCmdletAliases = @{ Whitelist = @('cd') } - } - ``` - This would configure the rule **PSAvoidUsingCmdletAliases** to ignore the alias `cd` (so using `cd` won't trigger a warning in that rule) ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,Whitelist%20%3D%20%40%28%27cd)). Not all rules have configurable parameters, but for those that do (like specifying compatible PowerShell versions in compatibility rules, etc.), the `Rules` key is how you pass those in. We’ll see more examples of this in **Advanced Use Cases**. - -**Enabling or Disabling Rules:** To selectively run rules, use **IncludeRules** or **ExcludeRules**. It’s generally recommended to use one of these approaches, not both, to avoid confusion – but if you do use both, note that any rule present in both lists will be *excluded* (ExcludeRules takes precedence) ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,ExcludeRules%20%3D%20%40%28%27PSAvoidUsingWriteHost%27%2C%27PSMissingModuleManifestField)). In practice: -- If you want to run a small *subset* of all rules (for example, only security-related rules), use **IncludeRules** to list them. Everything not listed is ignored. -- If you want to run most rules, but skip a few that don't apply, use **ExcludeRules** for those few, and let all others run. - -For example, your settings file might include: -```powershell -@{ - ExcludeRules = @('PSAvoidUsingWriteHost', 'PSUseWriteOutput') -} -``` -This would disable the **PSAvoidUsingWriteHost** and **PSUseWriteOutput** rules (perhaps you decide using Write-Host is acceptable in your project), and run all other default rules normally. Conversely, using IncludeRules: -```powershell -@{ - IncludeRules = @('PSAvoidHardcodingCredentials', 'PSUseApprovedVerbs') -} -``` -would run *only* those two rules (one that checks for hardcoded credentials and one that checks cmdlet naming) and no others ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=,)). - -**Adjusting Rule Severity Reporting:** Every PSScriptAnalyzer rule is defined with a severity level (information, warning, or error). These indicate how critical a finding is (with "Error" being most severe). Out of the box, most PSScriptAnalyzer rules are classified as warnings or information; truly critical issues (like syntax errors) might appear as errors. In the settings file, you can’t change a rule’s intrinsic severity, but you *can* control what severities are considered worth reporting: -- To **limit output to certain severities**, list them under the **Severity** key. For example: `Severity = @('Error','Warning')` will suppress informational messages ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Severity%3D%40%28%27Error%27%2C%27Warning%27%29%20ExcludeRules%3D%40%28%27PSAvoidUsingCmdletAliases%27%2C%20%27PSAvoidUsingWriteHost%27%29%20)). This is useful in CI/CD when you want to reduce noise (e.g., treat the build as passed even if only informational/style issues are present). In a development environment, you might use the full set including Information to get more suggestions, but in automation, you might filter out low-severity items. -- If you want to be extra strict, you could even *narrow* to `'Error'` only (meaning the build will only fail or report if an actual error-level issue is found). Or conversely, include `'Information'` if you want absolutely everything reported. -- Keep in mind that filtering by Severity happens **after** rules run ([Invoke-ScriptAnalyzer (PSScriptAnalyzer) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules#:~:text=)) ([Invoke-ScriptAnalyzer (PSScriptAnalyzer) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules#:~:text=You%20can%20specify%20one%20ore,more%20severity%20values)). PSScriptAnalyzer will still execute the rules; it just won't output the ones that don’t match the filter. If performance or efficiency is a concern (to not even run some rules), you should instead use Include/ExcludeRules to prevent running those rules at all. For instance, if you only care about error-level rules, you might identify which rules can produce warnings and exclude them instead of relying solely on the Severity filter. - -In summary, **configuring rules** via the settings file involves deciding which rules to run, which to skip, and what levels of findings to show. The table below summarizes the key configuration options and their effects: - -| **Key** | **Purpose** | **Example** | -|--------------------|------------------------------------------------------------|-----------------------------------------------------| -| `IncludeRules` | Run *only* these specific rules (names or wildcards). | `IncludeRules = @('PSUseApprovedVerbs', 'PSAvoid*')` | -| `ExcludeRules` | Run all except these specific rules. | `ExcludeRules = @('PSAvoidUsingWriteHost')` | -| `Severity` | Only report findings of these severities. | `Severity = @('Error', 'Warning')` | -| `IncludeDefaultRules` | When using custom rules, whether to also include built-in rules. | `IncludeDefaultRules = $true` (include both custom + default) | -| `CustomRulePath` | Path(s) to custom rule modules or scripts to load. | `CustomRulePath = 'path\to\MyRules.psm1'` | -| `RecurseCustomRulePath` | If true, search subfolders in CustomRulePath for rules. | `RecurseCustomRulePath = $true` | -| `Rules` | Rule-specific settings (hashtable of rule names to settings). | `Rules = @{ PSWhateverRule = @{ Param = 'Value' } }` | - -With these tools, you can fine-tune the analysis exactly as required. Next, we'll focus specifically on excluding rules and then on creating custom rules. - -## Rule Exclusions - -Sometimes you may want to **suppress certain rules** from running because they are not applicable to your scenario or perhaps yield too many false positives. Using the settings file to exclude rules is often preferable to peppering your code with suppression attributes, as it provides a single point of control. Here’s how to manage rule exclusions effectively: - -- **ExcludeRules Key:** As mentioned, this key takes an array of rule names to disable. The rule names correspond to the PSScriptAnalyzer rules (for example: PSAvoidUsingCmdletAliases, PSUseDeclaredVarsMoreThanAssignments, etc.). You can find the list of rule names via the `Get-ScriptAnalyzerRule` cmdlet or in [PSScriptAnalyzer’s rule documentation](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview#available-rules). In the settings file, list the rules to skip like so: - ```powershell - @{ - ExcludeRules = @('RuleName1', 'RuleName2', 'RuleName3') - } - ``` - For instance, to exclude the rules that discourage using Write-Host and ConvertTo-SecureString with plaintext, you could configure: - ```powershell - @{ - ExcludeRules = @('PSUseShouldProcessForStateChangingFunctions', - 'PSAvoidUsingConvertToSecureStringWithPlainText') - } - ``` - This example matches the Super-Linter default, which excludes two specific rules from the analysis ([ - PowerShell Gallery - | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview - ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=IncludeDefaultRules%C2%A0%3D%C2%A0%24,%27MyCustomRuleName%27)). The rest of the rules would still run (unless further limited by includes or severities). - -- **Wildcards:** You can use wildcard patterns to exclude groups of rules. For example, `ExcludeRules = @('PSAvoid*')` would exclude all rules whose names start with "PSAvoid". Use this with caution, as you might accidentally skip important rules. - -- **Precedence with IncludeRules:** If you happen to use both IncludeRules and ExcludeRules, note that an exclude will override an include. *“If a rule is in both IncludeRules and ExcludeRules, the rule will be excluded.”* ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,ExcludeRules%20%3D%20%40%28%27PSAvoidUsingWriteHost%27%2C%27PSMissingModuleManifestField)). This is logical – you explicitly told the analyzer to exclude it, so it won’t run even if it was also on your include list. In practice, try to stick to one approach (include-only or exclude-only) to keep the configuration clear. - -- **Temporary vs Permanent Exclusions:** If you find yourself excluding many rules, double-check if you truly need them all off. It's often better to exclude only what you must. Some teams use exclusions temporarily and aim to fix the underlying code issues so they can remove the exclusion later (for example, turning off a deprecated alias rule until they have time to refactor all uses of those aliases). - -- **Suppressing in Code vs. Settings:** PSScriptAnalyzer also supports suppressing a rule for a specific portion of code using annotations (the `[Diagnostics.CodeAnalysis.SuppressMessage()]` attribute in your script). Use that approach when a rule is generally useful, but a *specific instance* in code should be exempt. However, if you find you are suppressing the same rule in many places, it's a sign that maybe that rule should be globally excluded via the settings file (or that the team has consciously decided to not follow that particular guideline). - -In summary, **rule exclusions** in the settings file let you turn off unwanted rules globally for the analysis run. Keep the list of exclusions as short as possible to get maximum value from PSScriptAnalyzer, but do use it to disable rules that don’t make sense for your project. This ensures the output focuses only on relevant issues. - -## Custom Rules - -While PSScriptAnalyzer comes with a rich set of built-in rules, you may have organization-specific or project-specific guidelines that aren’t covered by the defaults. **Custom rules** allow you to extend PSScriptAnalyzer by writing your own rule logic. The settings file plays a crucial role in **registering these custom rules** so that PSScriptAnalyzer knows about them. - -**Creating a Custom Rule:** Custom rules are implemented as functions in a PowerShell module (.psm1) or script file. Key points for writing a custom rule module: - -- Each custom rule is a function that typically uses a verb like *Measure* or *Test* (for example, `Measure-MyCustomRule`). There's no strict naming requirement, but following a consistent verb-noun naming helps. In Microsoft’s examples, they use `Measure-` for custom rules ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=In%20this%20example%20the%20property,used%20for%20the%20property%20IncludeRules)). -- The function must accept either a `[ScriptBlockAst]` (AST of the script) or `[Token[]]` (array of tokens) as a parameter. Most custom rules work with the AST, as it provides a structured representation of the code. -- The function should return one or more **DiagnosticRecord** objects (or nothing if no issues found). A DiagnosticRecord is what PSScriptAnalyzer uses to represent a rule violation (with properties like Severity, ScriptName, Line, Message, etc.). -- Include comment-based help in your function with at least a `.Synopsis` (this becomes the rule's description) and `.Description` if needed. Also use the `[OutputType()]` attribute to declare that it outputs `DiagnosticRecord` objects ([Creating custom PSScriptAnalyzer rules](https://blog.ironmansoftware.com/psscriptanalyzer-custom-rules/#:~:text=,function%20that%20should%20be%20noted)). -- After defining the function(s) in the .psm1, make sure to **export** them (e.g., using `Export-ModuleMember -Function MyFunctionName`) ([Creating custom PSScriptAnalyzer rules](https://blog.ironmansoftware.com/psscriptanalyzer-custom-rules/#:~:text=Finally%2C%20we%20make%20sure%20to,the%20function%20from%20our%20module)). If you have multiple rules in one module, export each function that implements a rule. - -For example, imagine we want a custom rule to ensure no TODO comments are left in the code. We could create `MyCompany.AnalyzerRules.psm1` with a function `Measure-TodoComment` that scans the AST for comment tokens containing "TODO" and emits a DiagnosticRecord for each occurrence. Once that function is ready and exported from the module, we're set to wire it into PSScriptAnalyzer. - -**Using Custom Rules via Settings File:** The settings file needs to tell PSScriptAnalyzer where to find your custom rules and which ones to run. This is done with two keys we mentioned: `CustomRulePath` and (optionally) `IncludeRules`/`IncludeDefaultRules`. - -- **CustomRulePath:** Add this key with the path(s) to your custom rule module or script. For example: - ```powershell - @{ - CustomRulePath = @( - '.\Modules\MyCompany.AnalyzerRules\MyCompany.AnalyzerRules.psm1' - ) - } - ``` - You can specify multiple paths (e.g., if you have several custom rule modules) by using an array as shown. Relative paths are typically resolved relative to where you run PSScriptAnalyzer (e.g., your project root). Ensure the path is correct; if pointing to a module folder, you can just give the folder path (and it will load the module manifest if present). If pointing to a .psm1 file, include the full filename ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=In%20this%20example%20the%20property,used%20for%20the%20property%20IncludeRules)). - -- **IncludeRules for custom rules:** By default, when you provide custom rules, PSScriptAnalyzer might run *only* those custom rules (depending on how you configure IncludeDefaultRules). If you want to run all your custom rules, one easy way is to name them with a common prefix or verb and use a wildcard include. For instance, if all your custom rule functions start with `Measure-`, you could do: - ```powershell - @{ - CustomRulePath = '.\AnalyzerRules\CustomRules.psm1' - IncludeRules = @('Measure-*') - } - ``` - This tells PSSA to include any rules whose names match "Measure-*", which should rope in all functions from your custom module (assuming they use that naming convention) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=In%20this%20example%20the%20property,used%20for%20the%20property%20IncludeRules)) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=IncludeRules%20%20%20%20,%29)). - -- **Including default rules as well:** If you want to *add* custom rules on top of the standard ones (common case – you usually want both your rules and the built-ins), you should set `IncludeDefaultRules = $true` in the hashtable ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=IncludeDefaultRules%20%3D%20%24true)). By doing so, you ensure that the default rule set remains active alongside your custom ones. Then you have two options: - 1. **Run all default rules + all custom rules:** In this case, you might not even need an IncludeRules list; simply adding a CustomRulePath and setting IncludeDefaultRules `$true` might load everything (all defaults and all functions found in custom path). However, to be explicit, you could list some or all rules in `IncludeRules`. - 2. **Run a selection of default rules + specific custom rules:** List exactly which rules to run in `IncludeRules` (both default and custom). For example: - ```powershell - @{ - CustomRulePath = @('.\MyRules\MyRules.psm1') - IncludeDefaultRules = $true - IncludeRules = @( - # select some default rules - 'PSAvoidDefaultValueForMandatoryParameter', - 'PSUseApprovedVerbs', - # select custom rules by name - 'Measure-TodoComment', - 'Measure-SecretInScript' - ) - } - ``` - In this snippet, we point to a custom rules module, allow default rules, and explicitly include two specific default rules and two custom rules by name. Only those four rules would run. - -A real example from Microsoft’s documentation shows including both default and custom rules by mixing them in the IncludeRules list and enabling default rules: -```powershell -@{ - CustomRulePath = @( - '.\output\RequiredModules\DscResource.AnalyzerRules', - '.\tests\QA\AnalyzerRules\SqlServerDsc.AnalyzerRules.psm1' - ) - IncludeDefaultRules = $true - IncludeRules = @( - # Default rules - 'PSAvoidDefaultValueForMandatoryParameter', - 'PSAvoidDefaultValueSwitchParameter', - # Custom rules - 'Measure-*' - ) -} -``` -In this case, any rule in the custom modules matching `Measure-*` will run, plus the two named default rules (and no other default rules) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=%40,SqlServerDsc.AnalyzerRules.psm1%27)) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=%27PSAvoidDefaultValueForMandatoryParameter%27%20%27PSAvoidDefaultValueSwitchParameter%27)). - -**Using Custom Rules in VS Code:** If you're working in Visual Studio Code with the PowerShell extension, you can have it use your settings (and thus your custom rules) by pointing to the settings file in your workspace settings. In your `.vscode/settings.json`, set: -```json -{ - "powershell.scriptAnalysis.settingsPath": ".github/linters/.powershell-psscriptanalyzer.psd1", - "powershell.scriptAnalysis.enable": true -} -``` -This ensures that the editor, when providing real-time PSScriptAnalyzer feedback, uses your settings (including loading any custom rule modules) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=workspace%20settings%20file%20%28)). Without this, VS Code might only use default rules or its own default settings. - -**Testing Custom Rules:** After configuring, run `Invoke-ScriptAnalyzer` on a sample script that should trigger your custom rule to verify it's working. If it doesn't appear to run: -- Check that the path in CustomRulePath is correct. Try an absolute path to be sure. -- Ensure the module is not throwing errors on import (e.g., try an `Import-Module` on it). -- Confirm the function name or pattern is correctly listed in IncludeRules (if you use that). -- Make sure `IncludeDefaultRules` is set appropriately. If your custom rule names don't match any default rules and you **only** want custom rules, you might set `IncludeDefaultRules = $false` (or omit it, as false is default when IncludeRules is specified) to avoid running built-ins. -- If still not working, run PSScriptAnalyzer in verbose mode or check for any warnings about loading rules. There's also a built-in rule "PSScriptAnalyzerSettingsSchema" (available in newer versions) that can validate your settings file structure to catch mistakes ([[Resolved] PSSCriptAnalyzer warnings in VSCode](https://forums.ironmansoftware.com/t/resolved-psscriptanalyzer-warnings-in-vscode/3602#:~:text=rule%20definitions%20can%20be%20read,com%20%C2%B7%20PowerShell)). - -Custom rules empower you to enforce project-specific guidelines. Once set up in the settings file, they integrate seamlessly – from the command line, to editors, to CI pipelines – just like the built-in PSScriptAnalyzer rules. - -## Advanced Use Cases - -In this section, we explore advanced scenarios and fine-tuning techniques for PSScriptAnalyzer settings. These go beyond the basic include/exclude and custom rules to address project-specific needs and edge cases. - -### Fine-Tuning for Specific Projects - -Every project might have its own quirks. The settings file can be adjusted to handle those: - -- **Different Settings per Project**: If you maintain multiple projects (or modules) in one repository that have distinct guidelines, you can create multiple settings files. For example, if you have a module in one folder that requires strict rules and a script in another that requires a looser rule set, you might use `Invoke-ScriptAnalyzer -Path ModuleA -Settings .\ModuleA\PSScriptAnalyzerSettings.psd1` for one and a different settings file for the other. You could even automate this via separate CI jobs or scripts for each sub-project. Keep each settings file alongside its project files for clarity. -- **Built-in Presets**: PSScriptAnalyzer offers some built-in presets which are essentially pre-configured rule sets (invoked by passing a special value to the `-Settings` parameter, like "PSGallery", "DSC", or "CodeFormatting") ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Built)). While these aren't hashtable files you edit, it's good to know about them. For instance, the "PSGallery" preset focuses on rules relevant to publishing modules to PowerShell Gallery (e.g., required metadata), and "DSC" preset focuses on Desired State Configuration script guidelines. Advanced users sometimes start with a preset and then modify further via a custom settings file if needed. -- **Rule Parameter Customization**: We introduced the `Rules` hashtable for passing rule-specific settings. This is particularly useful for **edge cases** like: - - Whitelisting or blacklisting certain elements. e.g., *PSAvoidUsingCmdletAliases* rule normally warns on any alias usage. If your project is okay with a few specific aliases (like `ls` or `gc`), add them to a whitelist: - ```powershell - Rules = @{ PSAvoidUsingCmdletAliases = @{ Whitelist = @('ls','gc') } } - ``` - Now those aliases won't trigger the rule ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,Whitelist%20%3D%20%40%28%27cd)). - - Configuring compatibility checks. PSScriptAnalyzer has rules like **PSUseCompatibleCmdlets** which can check if your script's cmdlets exist in other PowerShell versions. That rule takes a `Compatibility` parameter (specifying target platforms/versions). You can set in the settings file: - ```powershell - Rules = @{ PSUseCompatibleCmdlets = @{ Compatibility = @('WindowsPowerShell_5.1', 'PowerShellCore_7.0') } } - ``` - This would make the rule check compatibility against Windows PowerShell 5.1 and PowerShell 7.0. Without such specification, it might default to some baseline or require manual parameter each time. - - Adjusting formatting rules. If you use the PSScriptAnalyzer formatting features (via `Invoke-Formatter` or the VSCode formatting), you might have formatting rules that accept settings (indentation style, etc.). Those too can often be configured via the settings file under the `Rules` key for the specific formatting rule. - -- **Excluding Files or Paths**: Unlike some linters, PSScriptAnalyzer’s settings file does not have a direct way to exclude specific file paths from analysis. File scoping is usually handled when invoking the tool (e.g., you choose what `-Path` to analyze, or you could script to skip certain files). However, you can simulate per-path rule exclusions by combining with the ability to suppress in-code or running separate passes. One advanced approach is to run PSScriptAnalyzer multiple times on different sets of files with different settings (perhaps via a script). For example, you might run it on all files normally, but on a specific problematic script with a special settings file that excludes a rule that doesn't play well with that script. - -- **Implicit vs Explicit Settings Usage**: Recall that if a file named `PSScriptAnalyzerSettings.psd1` is present in the directory you're analyzing, it will be automatically picked up ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)). In advanced scenarios, you may intentionally leverage this: - - If you have a repository with many projects, each could have its own settings file in its folder. A top-level build script could just call `Invoke-ScriptAnalyzer -Recurse` on each folder and let each one pick up its own settings implicitly. - - However, be cautious: if multiple settings files are present (say one in a parent folder and one in a subfolder), PSScriptAnalyzer will pick the one in the **exact path you specify**. It doesn't merge or traverse upward to find others. The *explicitly provided* `-Settings` always wins over implicit ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Invoke)). So if you want a single settings to apply to a whole repo, keep one at the root and call PSSA on the root. If you want different ones per sub-project, call PSSA on each sub-project folder separately. - -### Handling Edge Cases and Gotchas - -Even with a well-tuned configuration, you might encounter odd situations: - -- **Settings File Not Detected**: If you run `Invoke-ScriptAnalyzer` and it seems to ignore your settings (e.g., you still see warnings you meant to exclude), double-check: - - The `-Settings` parameter path is correct (if using explicitly). - - If relying on implicit discovery, ensure the file is named exactly `PSScriptAnalyzerSettings.psd1` (capitalization doesn't matter) and that you ran PSSA on the directory that contains that file. - - Make sure the .psd1 has valid PowerShell syntax. If there's a typo (like a missing `@` or a stray character), PSScriptAnalyzer may silently fall back to defaults. You can test by trying to manually dot-source the psd1 file in PowerShell (`. .\PSScriptAnalyzerSettings.psd1`) – it should import as a hashtable without error. If it errors out, fix the syntax. - - Check if there is a rule named **PSScriptAnalyzerSettingsSchema** in your PSSA version. This is a rule under discussion/implementation ([[RULE] PSScriptAnalyzerSettingsSchema · Issue #1279 - GitHub](https://github.com/PowerShell/PSScriptAnalyzer/issues/1279#:~:text=%5BRULE%5D%20PSScriptAnalyzerSettingsSchema%20%C2%B7%20Issue%20,so%20that%20I%20can)) ([[Resolved] PSSCriptAnalyzer warnings in VSCode](https://forums.ironmansoftware.com/t/resolved-psscriptanalyzer-warnings-in-vscode/3602#:~:text=rule%20definitions%20can%20be%20read,com%20%C2%B7%20PowerShell)) that, when enabled, could validate the structure of your settings file. If available, try enabling it to get feedback on your configuration file itself. - -- **Conflicting Settings**: Setting include and exclude rules that overlap, or severity filters that contradict included rules, can lead to confusion. For instance, if you set `Severity = @('Error')` but also `IncludeRules = @('PSAvoidUsingCmdletAliases')` (which is typically a Warning-level rule), you might wonder why you get no output – it's because the rule runs (due to IncludeRules) but its warning results are filtered out by the Severity setting (which allows only Errors). In such cases, PSScriptAnalyzer's logic is that the severity filter is applied after running all rules, effectively discarding any non-matching severities ([Invoke-ScriptAnalyzer (PSScriptAnalyzer) - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules#:~:text=You%20can%20specify%20one%20ore,more%20severity%20values)). The takeaway: ensure your rule inclusion/exclusion and severity filters are aligned. Usually, if using Severity filtering, you don't need to list rules of a filtered-out severity in IncludeRules. - -- **Performance Considerations**: In very large projects, running all rules can be slow. You might create a slim settings file for quick iterative checks (excluding some heavy rules or using severity filter to skip informational/style rules), and a full settings file for a thorough check before release. Similarly, if custom rules are slow, you could enable them conditionally. This is more of a pipeline optimization – for example, one could run a "fast lint" on each commit and a "full lint" nightly. - -- **Rule Updates in New PSScriptAnalyzer Versions**: The PSScriptAnalyzer module is periodically updated with new rules or changes. If you update it, new rules (with their default severities) might start running on your code. If you have an **ExcludeRules** list, those new rules will run because you didn’t explicitly exclude them (since they were unknown before). If you use **IncludeRules**, those new rules will *not* run (since your list is explicit). There’s a trade-off: - - Using *ExcludeRules* is future-proof in that you automatically get any new rules (which might be good, to catch new best practices) except ones you excluded. But you might need to update the exclude list if a new rule is noisy or not applicable. - - Using *IncludeRules* gives you a fixed set until you manually update it, preventing surprises from new rules. But you might miss out on beneficial new analysis until you revise the settings. - - **Best practice**: if your goal is to enforce all possible checks and only omit known problematic ones, favor ExcludeRules. If your goal is to enforce a very specific set of rules (compliance scenario, perhaps), use IncludeRules. - -- **Combining with Other Analyzers**: In advanced cases, projects might use multiple linters (e.g., perhaps PSScriptAnalyzer plus a JSON linter for config files, etc.). If using a unified tool like Super-Linter or Mega-Linter, make sure the config file is in the correct location and named as expected for PSScriptAnalyzer. For instance, as noted earlier, Super-Linter expects `.github/linters/.powershell-psscriptanalyzer.psd1`. If you name it differently, you might need to configure the linter to know the custom path (Super-Linter allows some override variables if needed, but following convention is easiest). - -With careful configuration, PSScriptAnalyzer can handle most project requirements. You can mix and match these strategies—just keep track of what you’ve configured so that the behavior remains predictable. - -## Automation and CI/CD Integration - -Integrating PSScriptAnalyzer into your automated build or deployment pipeline is an excellent way to prevent code with issues from being merged or released. Here’s how you can use the hashtable-based settings file in various CI/CD scenarios: - -- **GitHub Actions (Super-Linter)**: GitHub offers a **Super-Linter** action that runs multiple linters, including PSScriptAnalyzer, in one go. If you use Super-Linter, simply placing your settings file at `.github/linters/.powershell-psscriptanalyzer.psd1` in your repo is all you need – the action will automatically detect and use it ([ - PowerShell Gallery - | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview - ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=Documentation%3A%20https%3A%2F%2Fgithub.com%2FPowerShell%2FPSScriptAnalyzer%2Fblob%2Fmaster%2Fdocs%2Fmarkdown%2FInvoke,RecurseCustomRulePath%3D%27path%5Cof%5Ccustomrules%27%20Severity%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3D)). Super-Linter will run PSScriptAnalyzer against your `.ps1`, `.psm1`, etc., files using the rules defined in that file. Ensure that the file is named exactly as expected (note the leading dot and specific name). In your GitHub Actions workflow yaml, you might have: - ```yaml - - uses: github/super-linter@vX.Y.Z - with: - languages: 'POWERSHELL' # (and others as needed) - ``` - That’s it – the configuration is picked up by convention. - -- **GitHub Actions (Dedicated PSSA Action)**: There is also a dedicated action, **microsoft/psscriptanalyzer-action**, which focuses on PSScriptAnalyzer and produces outputs like SARIF (for GitHub code scanning). With this action, you can specify the `settings` input to point to your settings file. For example: - ```yaml - - name: Run PSScriptAnalyzer - uses: microsoft/psscriptanalyzer-action@v1 - with: - path: './**/*.ps1' # or path to your scripts directory - settings: .github/linters/.powershell-psscriptanalyzer.psd1 - failOnError: true # (if you want to fail the job on any errors) - ``` - The `settings` parameter accepts a path to your psd1 file ([GitHub - microsoft/psscriptanalyzer-action: GitHub Action to run PSScriptAnalyzer to your repository and produce a SARIF file](https://github.com/microsoft/psscriptanalyzer-action#:~:text=settings)), so you can keep your config in the repo. This action also supports additional options like producing a SARIF report that integrates with GitHub’s code scanning alerts. Check the action’s documentation for details, such as the `enableExit` flag (which makes the action exit with a non-zero code equal to the number of errors, causing the pipeline to fail if any errors are found) ([GitHub - microsoft/psscriptanalyzer-action: GitHub Action to run PSScriptAnalyzer to your repository and produce a SARIF file](https://github.com/microsoft/psscriptanalyzer-action#:~:text=enableExit)). - -- **Azure Pipelines**: In Azure DevOps or other CI systems, you might not have a pre-built PSScriptAnalyzer task by default, but it's easy to use via PowerShell scripts. For example, in an Azure Pipeline YAML: - ```yaml - - task: PowerShell@2 - inputs: - targetType: inline - script: | - Install-Module PSScriptAnalyzer -Scope CurrentUser -Force - Invoke-ScriptAnalyzer -Path "$(System.DefaultWorkingDirectory)\MyProject" -Recurse -Settings "$(System.DefaultWorkingDirectory)\MyProject\.github\linters\.powershell-psscriptanalyzer.psd1" -ErrorAction Continue - if ($LASTEXITCODE -ne 0) { - Write-Host "PSScriptAnalyzer found errors." - Exit 1 - } - ``` - This installs PSScriptAnalyzer (if not already available on the agent), runs it with the settings file, and uses the exit code or $LASTEXITCODE to decide if the pipeline should fail. You might also capture the results and publish them as artifacts or test results. (Note: By default, `Invoke-ScriptAnalyzer` does not set a distinct exit code for findings; one way to get an exit code is to use the `-ErrorAction` as shown combined with `failOnError`-like logic, or use the PSScriptAnalyzer `EnableExit` switch introduced in newer versions which directly exits with number of errors ([GitHub - microsoft/psscriptanalyzer-action: GitHub Action to run PSScriptAnalyzer to your repository and produce a SARIF file](https://github.com/microsoft/psscriptanalyzer-action#:~:text=enableExit)).) - -- **Other CI Tools**: In Jenkins or other CI systems, the approach is similar: run a PowerShell step that calls PSScriptAnalyzer with the `-Settings` pointing to your file, then parse the output or exit code. PSScriptAnalyzer can output results to the console or to a file (it supports formatting output as plain text, JSON, or even SARIF using the `-OutPath` and `-Format` parameters). For instance, you could output SARIF and feed it into a code analysis tool or archive it for review. - -- **Failing the Build on Violations**: Decide what level of violations should cause a build to fail. A common practice: - - Fail on any "Error" severity issues (these often indicate likely bugs or serious problems). - - Optionally, allow "Warnings" but still succeed the build (maybe just log them), or fail if you want to enforce a stricter standard. - - Ignore "Information" in CI to reduce noise. - - Since your settings file can filter severities, one strategy is to maintain a separate CI-specific settings that sets `Severity = @('Error','Warning')` (if you want to treat warnings as needing attention) or just `'Error'` for very strict pipelines ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=%40%7B%20,Error%27%29)). However, it's often sufficient to use one settings file and handle severity in the pipeline script: e.g., run PSSA with all severities but then post-process results – if any errors, fail; if only warnings, perhaps just warn. This depends on your build governance policy. - -- **Continuous Integration Example**: Suppose we want to ensure no new PSScriptAnalyzer errors/warnings get introduced. We could have a step in CI that runs PSSA and outputs results. If output is not empty (or contains certain severities), mark the build unstable. In GitHub Actions, if using the PSScriptAnalyzer action with `failOnError: true` (or using `enableExit` internally), the action will handle failing on error-level findings automatically ([GitHub - microsoft/psscriptanalyzer-action: GitHub Action to run PSScriptAnalyzer to your repository and produce a SARIF file](https://github.com/microsoft/psscriptanalyzer-action#:~:text=enableExit)). For warnings, you might need a custom step or adjust the action's parameters (some might offer `failOnWarning`). Always consult the specific action/tool docs. - -- **Automated Formatting**: While not exactly analysis, PSScriptAnalyzer includes an `Invoke-Formatter` cmdlet that can auto-format code according to rules. This can be integrated into pre-commit hooks or CI (for example, to fail if code is not formatted). The formatting rules can also be controlled via a settings file (using the `Rules` key for the formatting settings). An advanced CI integration could run the formatter in a PR workflow, and either auto-commit the changes or advise the user to run formatting. This goes hand-in-hand with analysis: analyze to catch issues, format to fix style issues automatically. - -- **SARIF and Security Scanning**: PSScriptAnalyzer isn't a security scanner per se, but it can detect some security relevant patterns (like potential credential leaks, use of dangerous commands, etc.). By outputting to SARIF (Static Analysis Results Interchange Format) and uploading that to platforms like GitHub or Azure, you can integrate PSSA results into code scanning dashboards. The GitHub action we discussed can output SARIF by default, and GitHub Advanced Security can pick it up to show alerts in the Security tab of the repo. - -**Tip:** When integrating into CI, run PSScriptAnalyzer on the **same PowerShell version** that your team uses for development, or the environment you target. The set of rules and their behavior is the same across PS versions for a given PSSA version, but if you use the compatibility rules, the runtime matters (e.g., running on PowerShell 7 analyzing for Windows PowerShell compatibility is fine, but just be aware of what environment the analysis is running in for path references and such). - -By automating PSScriptAnalyzer in CI/CD, you create a quality gate that helps maintain your PowerShell code standard. The settings file ensures this is done consistently every time. As developers push code, they get immediate feedback if something doesn't meet the team's standards, and they can fix it before merging – resulting in cleaner, more reliable code in your main branches. - -## Best Practices - -To wrap up, here are some best practices and tips for using a hashtable-based PSScriptAnalyzer settings file effectively: - -- **Start with Defaults, Then Tweak**: Begin by running PSScriptAnalyzer with the default rules on your codebase to see what it flags. Use that to inform your settings. Disable rules only if you have a good reason. It's better to be informed of an issue and decide to ignore it than to never know it at all. That said, if a rule consistently flags things that are acceptable in your context, exclude it in the settings to reduce noise. - -- **Use Exclusions Sparingly**: Each excluded rule is a class of issues you won't hear about. Keep the **ExcludeRules** list as short as possible. For instance, you might exclude style preferences you don't agree with, but think twice before excluding rules related to security or correctness. If a rule is noisy but important, consider fixing the underlying code instead of excluding the rule. - -- **Leverage Severity for Focus**: Especially in CI, consider filtering out informational messages. Many teams configure `Severity = @('Error','Warning')` in the settings (or only errors) ([ - PowerShell Gallery - | .github/linters/.powershell-psscriptanalyzer.psd1 0.0.2-Preview - ](https://www.powershellgallery.com/packages/SecretManagement.Hashicorp.Vault.KV/0.0.2-Preview/Content/.github%5Clinters%5C.powershell-psscriptanalyzer.psd1#:~:text=Severity%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%C2%A0%3D%C2%A0%40%28%20%27Error%27%20%27Warning%27%20%29%20IncludeDefaultRules%C2%A0%3D%C2%A0%24,%27PSUseShouldProcessForStateChangingFunctions%27%2C%20%27PSAvoidUsingConvertToSecureStringWithPlainText)) to focus on the more significant issues. You can still run a full analysis locally with all severities if you want the additional info. Another approach is to use severity filtering only in CI (with a separate settings or `-Severity` param) while keeping the dev-time analysis fully verbose. - -- **Keep the Settings File with the Code**: Store the `.psd1` in your repository (as we did under `.github/linters/` or at the project root). This way, changes to it are versioned. When updating PSScriptAnalyzer or altering rules, any adjustments you make to the configuration travel along with those changes in source control. New developers pulling the repo will get the config and their VSCode can pick it up, etc. - -- **Document Your Choices**: Within the settings file, use comments to note why certain rules are excluded or certain settings are in place. This helps future maintainers understand the rationale. The psd1 supports comments (as it's just text), as shown in the VSCode example settings file with lots of commented explanations ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=%40%7B%20,Severity%20%3D%20%40%28%27Error%27%2C%27Warning)) ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,ExcludeRules%20%3D%20%40%28%27PSAvoidUsingWriteHost%27%2C%27PSMissingModuleManifestField)). Don’t hesitate to annotate it. - -- **Validate Custom Rules**: If you wrote custom rules, include unit tests for them if possible (for example, using Pester to run the rule function on sample ASTs). This ensures your custom rules work as expected and continue to work when you update PSScriptAnalyzer (or PowerShell itself). Also, if sharing custom rules with others, provide documentation for those rules. - -- **Regularly Update PSScriptAnalyzer**: New versions may bring improvements and new rules. Keep an eye on the PSScriptAnalyzer release notes. When updating, run it on your code with the new version in a non-blocking way to see if new warnings/errors appear. Adjust your code or settings accordingly. For example, if a new rule flags something you intentionally do, you might add that rule to ExcludeRules after upgrading. - -- **Use CI to Enforce Standards**: Make the CI builds fail on PSScriptAnalyzer errors (and optionally on warnings). This creates an incentive to keep code clean. However, avoid making it so strict that it impedes progress – find a balance. Often teams treat warnings as "should fix" but not blockers, while errors are "must fix". If so, set up your pipeline accordingly (fail on errors, and maybe allow warnings but still report them). - -- **Troubleshooting Tips**: - - If you think the settings file isn't working, run `Invoke-ScriptAnalyzer` with the `-Verbose` flag. It will often print information about which settings file is loaded (or if none) and what rules it's executing. This can reveal, for example, that it's not finding your file, or a rule name was not recognized. - - Ensure no duplicate keys or conflicting keys in the hashtable. Each key should appear at most once. If you accidentally have two `Severity` entries, for example, the latter might overwrite the former or the file might fail to parse. - - Remember that the settings file is essentially a PowerShell script that returns a hashtable. This means you could even have logic in it (though not recommended for simplicity and security). For instance, technically you could do something like: - ```powershell - @{ - Severity = $([Environment]::GetEnvironmentVariable('PSSASeverity') -split ',') - } - ``` - This is advanced usage and generally discouraged, but it's worth noting the file is evaluated by PowerShell. Most will keep it static. - - - On rare occasions, a rule might throw an unexpected error when running on certain code (an edge case bug in PSScriptAnalyzer). If you encounter this, you can exclude that rule as a workaround and report the issue to the PSScriptAnalyzer project. Use `-ExcludeRule` on the command as a quick test to identify which rule is problematic, then permanently exclude in settings until a fix is available. - -- **Security of the Settings File**: Treat the settings file as part of your codebase. It doesn't typically contain secrets or anything sensitive (unless you, say, whitelist some secret in it which you shouldn't). However, because it can theoretically execute code (like any .psd1), ensure only trusted persons can modify it, especially in environments where it might run automatically (CI). In practice, keep it in source control and use code reviews for changes. - -By following these best practices, you can harness PSScriptAnalyzer to its fullest, maintaining high quality PowerShell code with a configuration that suits your team's needs. A well-tuned settings file becomes a powerful ally in your development process – catching issues early, enforcing standards, and even educating team members about best practices (since each rule often comes with guidance on how to improve the code). - -**References:** The information in this guide was compiled from official PSScriptAnalyzer documentation and community sources. Key references include the Microsoft Learn docs for PSScriptAnalyzer ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=The%20following%20example%20excludes%20two,other%20than%20Error%20and%20Warning)) ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=Implicit)), the PSScriptAnalyzer GitHub README ([PSScriptAnalyzer/README.md at main · PowerShell/PSScriptAnalyzer · GitHub](https://github.com/PowerShell/PSScriptAnalyzer/blob/master/README.md#:~:text=PSScriptAnalyzer%20is%20a%20static%20code,suggests%20possible%20solutions%20for%20improvements)), example settings files provided by the PowerShell extension ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=%40%7B%20,Severity%20%3D%20%40%28%27Error%27%2C%27Warning)) ([Where do I place the PSScriptAnalyzerSettings.psd1 file so the settings will be applied for all user accounts? : r/PowerShell](https://www.reddit.com/r/PowerShell/comments/lt5w8q/where_do_i_place_the_psscriptanalyzersettingspsd1/#:~:text=,ExcludeRules%20%3D%20%40%28%27PSAvoidUsingWriteHost%27%2C%27PSMissingModuleManifestField)), and community blog posts about custom rules ([Using PSScriptAnalyzer - PowerShell | Microsoft Learn](https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer?view=ps-modules#:~:text=%40,SqlServerDsc.AnalyzerRules.psm1%27)) and CI integration. These resources offer deeper insights and examples for those looking to further explore PSScriptAnalyzer's capabilities. Enjoy clean, robust PowerShell scripting with PSScriptAnalyzer! diff --git a/Rules.md b/Rules.md new file mode 100644 index 0000000..b7848bd --- /dev/null +++ b/Rules.md @@ -0,0 +1,116 @@ +# PSScriptAnalyzer Settings File Format Documentation + +This document describes the format and usage of the hashtable-based settings file +for PSScriptAnalyzer. The file is used by the GitHub action to customize analysis. + +## File Location and Basic Setup + +Place the file at: +``` +.github/linters/.powershell-psscriptanalyzer.psd1 +``` +The file is a PowerShell data file (.psd1) that returns a hashtable. For example: +```powershell +@{ + Severity = @('Error','Warning') + ExcludeRules = @('PSAvoidUsingWriteHost') +} +``` +This example sets the severity filter and excludes a specific rule. + +## Key Configuration Options + +- **IncludeRules** + A list of rules to run. Wildcards (e.g. `PSAvoid*`) are supported. + +- **ExcludeRules** + A list of rules to skip. Excludes take precedence over include lists. + +- **Severity** + Filters output by severity. Allowed values include `Error`, `Warning`, and + `Information`. + +- **IncludeDefaultRules** + A Boolean switch to include default rules when using custom rules. + +- **CustomRulePath** + One or more paths to custom rule modules or scripts. These extend PSScriptAnalyzer. + +- **RecurseCustomRulePath** + Boolean to search subdirectories of the custom rule path(s) for more rule files. + +- **Rules** + A nested hashtable for rule-specific settings. Use it to pass parameters to rules. + For example: + ```powershell + Rules = @{ + PSAvoidUsingCmdletAliases = @{ Whitelist = @('ls','gc') } + } + ``` + +## Configuring Custom Rules + +Custom rules are implemented in modules (.psm1) or scripts. They must export +functions that return DiagnosticRecord objects. Specify their location using +**CustomRulePath**. Use **IncludeDefaultRules = $true** if you want to run both +default and custom rules. + +For example: +```powershell +@{ + CustomRulePath = @('.\Modules\MyCustomRules.psm1') + IncludeDefaultRules = $true + IncludeRules = @('PSUseApprovedVerbs', 'Measure-*') +} +``` + +## Advanced Use Cases + +- **Selective Rule Execution** + Use either **IncludeRules** or **ExcludeRules** to control which rules run. + They help reduce noise in the analysis output. + +- **Rule-Specific Parameters** + Configure individual rules via the **Rules** key. Pass any required + parameters to fine-tune rule behavior. + +- **Multiple Settings Files** + In a multi-project repo, use separate settings files for each project and + run PSScriptAnalyzer with the appropriate file. + +- **Dynamic Settings** + Although not recommended, you can include minimal logic in the .psd1 file. + For example, using environment variables to adjust settings dynamically. + +## Automation and CI/CD Integration + +This settings file is designed to be used with automated pipelines. + +- **GitHub Actions** + The Super-Linter action automatically picks up the file from the above path. + Alternatively, use a dedicated PSScriptAnalyzer action with the settings input. + +- **Azure Pipelines** + Run a PowerShell task that installs PSScriptAnalyzer and points to the settings file. + Exit codes can be used to fail the build on errors. + +- **Other CI Tools** + Any CI system can invoke `Invoke-ScriptAnalyzer` with the `-Settings` parameter + to use this configuration. + +## Best Practices + +- **Version Control**: Store the settings file in your repository to keep configuration + consistent across environments. +- **Minimal Exclusions**: Exclude only rules that are not applicable to your project. +- **Documentation**: Use comments in the settings file to explain why certain rules are + included or excluded. +- **Regular Updates**: Update your settings when you upgrade PSScriptAnalyzer or change your + project requirements. + +## Links and References + +- [PSScriptAnalyzer Documentation](https://learn.microsoft.com/powershell/module/psscriptanalyzer/) +- [GitHub Super-Linter](https://github.com/github/super-linter) +- [PSScriptAnalyzer GitHub Repository](https://github.com/PowerShell/PSScriptAnalyzer) +- [Custom Rules in PSScriptAnalyzer](https://docs.microsoft.com/powershell/scripting/developer/hosting/psscriptanalyzer-extensibility) diff --git a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 index 6998e75..6ff16bb 100644 --- a/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 +++ b/scripts/tests/PSScriptAnalyzer/PSScriptAnalyzer.Tests.ps1 @@ -29,13 +29,14 @@ BeforeDiscovery { Description = $ruleObject.Description Skip = $ruleObject.RuleName -in $settings.ExcludeRules <# - RuleName : PSDSCUseVerboseMessageInDSCResource - CommonName : Use verbose message in DSC resource - Description : It is a best practice to emit informative, verbose messages in DSC resource functions. This helps in debugging issues when a DSC configuration is executed. - SourceType : Builtin - SourceName : PSDSC - Severity : Information - ImplementingType : Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.UseVerboseMessageInDSCResource + RuleName : PSDSCUseVerboseMessageInDSCResource + CommonName : Use verbose message in DSC resource + Description : It is a best practice to emit informative, verbose messages in DSC resource functions. + This helps in debugging issues when a DSC configuration is executed. + SourceType : Builtin + SourceName : PSDSC + Severity : Information + ImplementingType : Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules.UseVerboseMessageInDSCResource #> } ) From d6d03ba6cfc8fa1a1274999313c57f9c95834a6d Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 12:54:49 +0100 Subject: [PATCH 11/14] Update Rules.md to specify PowerShell code block for file location --- Rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rules.md b/Rules.md index b7848bd..ad5b739 100644 --- a/Rules.md +++ b/Rules.md @@ -6,7 +6,7 @@ for PSScriptAnalyzer. The file is used by the GitHub action to customize analysi ## File Location and Basic Setup Place the file at: -``` +```powershell .github/linters/.powershell-psscriptanalyzer.psd1 ``` The file is a PowerShell data file (.psd1) that returns a hashtable. For example: From 12b3e732016303c2379fe30592767ff128386264 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 13:01:03 +0100 Subject: [PATCH 12/14] Update README.md and add SettingsFileDocumentation.md for Invoke-ScriptAnalyzer action --- README.md | 108 +++++++++++++++++++++-- Rules.md => SettingsFileDocumentation.md | 0 action.yml | 6 +- 3 files changed, 103 insertions(+), 11 deletions(-) rename Rules.md => SettingsFileDocumentation.md (100%) diff --git a/README.md b/README.md index d560186..62de99c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,109 @@ -# Template-Action +```markdown +# Invoke-ScriptAnalyzer (by PSModule) -A template repository for GitHub Actions +This repository contains a GitHub Action that runs PSScriptAnalyzer on your code. +The action analyzes PowerShell scripts using a hashtable-based settings file to +customize rule selection, severity filtering, and custom rule inclusion. -## Usage +> **Note:** This repository includes automated tests that run via Pester to ensure +> your settings file is working as expected. -### Inputs +## Action Details -### Secrets +- **Name:** Invoke-ScriptAnalyzer (by PSModule) +- **Description:** Runs PSScriptAnalyzer on the code. +- **Author:** PSModule +- **Branding:** + Icon: `check-square` + Color: `gray-dark` -### Outputs +## Inputs -### Example +| Input | Description | Required | Default | +|---------------------|-------------------------------------------------------------------|----------|-----------------------------------------------------------------------------| +| **Path** | The path to the code to test. | Yes | `${{ github.workspace }}` | +| **Settings** | The type of tests to run: `Module`, `SourceCode`, or `Custom`. | No | `Custom` | +| **SettingsFilePath**| If `Custom` is selected, the path to the settings file. | No | `${{ github.workspace }}/.github/linters/.powershell-psscriptanalyzer.psd1` | + +## Outputs + +| Output | Description | Value | +|---------|---------------------------------------|--------------------------------------------| +| passed | Indicates if the tests passed. | `${{ steps.test.outputs.Passed }}` | + +## Files Overview + +- **action.yml** + Describes the action inputs, outputs, and run steps. The action uses a + composite run steps approach with two main steps: + 1. **Get test paths:** Uses a script to resolve paths and settings. + 2. **Invoke-Pester:** Runs Pester tests against PSScriptAnalyzer. + +- **scripts/main.ps1** + Determines the correct settings file path based on the test type. It + supports testing a module, source code, or using a custom settings file. + +- **scripts/tests/PSScriptAnalyzer/** + Contains Pester tests that run PSScriptAnalyzer using the provided settings + file. The tests check for issues reported by PSScriptAnalyzer based on rule + configuration. + +## How It Works + +1. **Path Resolution:** + The action reads inputs and determines the code path, test path, and the + settings file path. For custom settings, it uses the file at: + ``` + .github/linters/.powershell-psscriptanalyzer.psd1 + ``` + Otherwise, it uses a default settings file from the test folder. + +2. **Pester Testing:** + The tests import the settings file and use `Invoke-ScriptAnalyzer` to scan + the code. Each rule is evaluated, and if a rule violation is found, the test + will fail for that rule. Rules that are marked to be skipped (via exclusions + in the settings file) are automatically skipped in the test. + +3. **Automation:** + Designed for CI/CD, this action integrates with GitHub Actions, Azure Pipelines, + and other systems. The settings file customizes analysis, letting you control + rule inclusion, severity filtering, and custom rule paths. + +## Example Workflow + +Below is an example workflow configuration using this action: ```yaml -Example here +name: Analyze PowerShell Code + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Invoke PSScriptAnalyzer + uses: PSModule/Invoke-ScriptAnalyzer@v1 + with: + Path: ${{ github.workspace }} + Settings: Custom + SettingsFilePath: ${{ github.workspace }}/.github/linters/.powershell-psscriptanalyzer.psd1 +``` + +## Appendix: Settings File Documentation + +For detailed documentation on the format of the settings file, see the +[Settings File Documentation](./SettingsFileDocumentation.md) file. + +## References and Links + +- [PSScriptAnalyzer Documentation](https://learn.microsoft.com/powershell/module/psscriptanalyzer/) +- [GitHub Super-Linter](https://github.com/github/super-linter) +- [PSScriptAnalyzer GitHub Repository](https://github.com/PowerShell/PSScriptAnalyzer) +- [Custom Rules in PSScriptAnalyzer](https://docs.microsoft.com/powershell/scripting/developer/hosting/psscriptanalyzer-extensibility) + +## License + +This project is licensed under the MIT License. ``` diff --git a/Rules.md b/SettingsFileDocumentation.md similarity index 100% rename from Rules.md rename to SettingsFileDocumentation.md diff --git a/action.yml b/action.yml index 03c4eeb..2ef0cde 100644 --- a/action.yml +++ b/action.yml @@ -1,5 +1,5 @@ -name: Test-PSModule (by PSModule) -description: Test a PowerShell module before publishing the module to the PowerShell Gallery. +name: Invoke-ScriptAnalyzer (by PSModule) +description: Runs PSScriptAnalyzer on the code. author: PSModule branding: icon: check-square @@ -15,7 +15,7 @@ inputs: required: false default: 'Custom' SettingsFilePath: - description: The path to the settings file. + description: If 'Custom' is selected, the path to the settings file. required: false default: ${{ github.workspace }}/.github/linters/.powershell-psscriptanalyzer.psd1 From b3ab419f6ec524fa6ab77dafde1121d2d89623e6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 13:01:23 +0100 Subject: [PATCH 13/14] Remove unnecessary markdown formatting and license section from README.md --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 62de99c..d4d90a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -```markdown # Invoke-ScriptAnalyzer (by PSModule) This repository contains a GitHub Action that runs PSScriptAnalyzer on your code. @@ -102,8 +101,3 @@ For detailed documentation on the format of the settings file, see the - [GitHub Super-Linter](https://github.com/github/super-linter) - [PSScriptAnalyzer GitHub Repository](https://github.com/PowerShell/PSScriptAnalyzer) - [Custom Rules in PSScriptAnalyzer](https://docs.microsoft.com/powershell/scripting/developer/hosting/psscriptanalyzer-extensibility) - -## License - -This project is licensed under the MIT License. -``` From 09d4a3a24b4d18906f9e822f00338d8dba116fc9 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 21 Feb 2025 13:06:21 +0100 Subject: [PATCH 14/14] Specify PowerShell syntax highlighting for settings file path in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4d90a5..2bcc8a5 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ customize rule selection, severity filtering, and custom rule inclusion. 1. **Path Resolution:** The action reads inputs and determines the code path, test path, and the settings file path. For custom settings, it uses the file at: - ``` + ```powershell .github/linters/.powershell-psscriptanalyzer.psd1 ``` Otherwise, it uses a default settings file from the test folder.