Skip to main content

Quality Gates & Code Standards

This page documents the quality gates and code standards enforced in the NinjaOne module through PSScriptAnalyzer and custom rules.

Overview

Code quality is maintained through:

  • PSScriptAnalyzer - Static analysis rules from the PSScriptAnalyzer module
  • Custom Rules - Project-specific rules that enforce module conventions
  • Parameter Documentation - Inline descriptions for public and internal function parameters
  • Help Documentation - Comment-based help for all public functions

Running Quality Checks

Full Analysis

Run all quality checks including tests and analysis:

pwsh -File .\DevOps\Quality\test.ps1

Analysis Only

Run PSScriptAnalyzer without tests:

pwsh -File .\DevOps\Quality\run-pssa.ps1

The analyzer uses PSScriptAnalyzerSettings.psd1 and excludes:

  • CustomRules - Custom rule definitions themselves
  • output - Generated build artifacts
  • Modules - Third-party bundled modules
  • test-rules.ps1 - Test helper for PSSA itself

Custom Rules

Custom rules are defined in CustomRules/ and loaded automatically by the test and analysis scripts.

PSMissingParameterInlineComment

Rule: All parameters in public functions must have inline descriptions.

Purpose: Ensures comprehensive documentation for API consumers. These descriptions are extracted during help generation and appear in the generated .mdx documentation files.

Example - Bad:

function Get-NinjaOneDevice {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$deviceId,
[string]$filter
)

Example - Good:

function Get-NinjaOneDevice {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
# The device ID to retrieve information for.
[string]$deviceId,
# Optional filter expression see the filters documentation for more information.
[string]$filter
)

For Public Functions:

  • Required - All parameters must have inline descriptions
  • These automatically appear in generated documentation

For Internal Functions:

  • Parameters can be suppressed if descriptions aren't needed
  • Use suppression attributes (see below)

Suppressing Rules

Some internal scripts (DevOps tooling, private functions, test utilities) don't require full parameter documentation. Use diagnostic suppression attributes to explicitly allow this.

Suppression Syntax

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('RuleName', '', Justification = 'reason')]

Script-Level Suppression

Apply at the top of the script file to suppress the rule for all functions:

#requires -Version 7
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSMissingParameterInlineComment', '', Justification = 'Internal DevOps script does not require parameter descriptions.')]
param()

function Build-Module {
param(
[string]$config,
[switch]$verbose
)
# ...
}

Function-Level Suppression

Apply immediately before the function declaration to suppress only that function:

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSMissingParameterInlineComment', '', Justification = 'Private helper function used internally.')]
function ConvertTo-JsonString {
param(
[hashtable]$data,
[int]$depth
)
# ...
}

Nested Function Suppression

Each nested function requires its own suppression:

function Test-Configuration {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSMissingParameterInlineComment', '', Justification = 'Nested test helper.')]
function ValidateInput {
param([string]$value)
return $value -ne $null
}

# Main logic
}

Built-In Rules

Common PSScriptAnalyzer rules enforced in this project:

RuleSeverityPurpose
PSUseSingularNounsWarningEncourages singular naming for non-collection objects
PSAvoidDefaultValueSwitchParameterWarningPrevents switch parameters with default values
PSUseCompatibleSyntaxErrorEnsures PowerShell 7+ compatibility
PSAvoidUsingPositionalParametersWarningEncourages named parameters in function calls
PSUseOutputTypeCorrectlyWarningValidates [OutputType()] attributes
PSAvoidUsingInvokeExpressionErrorPrevents security vulnerabilities with dynamic code
PSAvoidUsingConvertToSecureStringWithPlainTextErrorPrevents plaintext password exposure

For the full list, see PSScriptAnalyzerSettings.psd1.

Parameter Documentation Examples

Minimal Good Parameter

param(
[Parameter(Mandatory)]
[string]$deviceId # Device identifier
)

Comprehensive Parameter

param(
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[Alias('DeviceID', 'ID')]
[object[]]$device, # Device object(s) to retrieve information for

[Parameter(ValueFromPipelineByPropertyName)]
[ValidateSet('Active', 'Inactive', 'Archived')]
[string]$status = 'Active', # Filter by device status; defaults to Active

[switch]$includeDetails, # Include detailed device information

[int]$pageSize = 100 # Results per page; defaults to 100
)

Key points:

  • First line: Brief purpose or value description
  • For switches: Explain what enabling the switch does
  • For parameters with defaults: Explain the default behavior
  • For enums: List valid options if not self-explanatory
  • For collections: Clarify if single or multiple values accepted

Documentation Quality Checklist

Before submitting a PR with new or modified public functions:

  • All parameters have inline descriptions (comments after parameter)
  • Descriptions follow the style guide above
  • Comment-based help is present and accurate
  • .PARAMETER sections match parameter descriptions
  • PSScriptAnalyzer passes without warnings
  • No placeholder text in generated docs (e.g., Fill parameter Description)

Troubleshooting Quality Failures

"PSScriptAnalyzer found violations"

Run the analyzer to see details:

pwsh -File .\DevOps\Quality\run-pssa.ps1

"Directory scan shows 0 violations but file scan shows many"

When running Invoke-ScriptAnalyzer with a directory path, you must use the -Recurse parameter to scan subdirectories:

# ❌ This won't find violations in subdirectories
Invoke-ScriptAnalyzer -Path .\Source

# ✅ This will find all violations
Invoke-ScriptAnalyzer -Path .\Source -Recurse

The run-pssa.ps1 script uses Get-ChildItem -Recurse | Invoke-ScriptAnalyzer, which correctly processes all files.

"Rule doesn't apply to my function"

Verify the rule scope:

  • PSMissingParameterInlineComment applies to all functions (public, private, internal)
  • Exceptions are handled through suppression attributes
  • If suppressing, use a clear justification explaining why the rule doesn't apply

"Suppression attribute isn't working"

Common issues:

  1. Wrong rule name: Must match exactly (e.g., PSMissingParameterInlineComment, not PSMissingParameter)

  2. Wrong indentation: Ensure the attribute is immediately before the function:

    # ❌ Won't work - extra line
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(...)]

    function MyFunction {

    # ✅ Works - directly before function
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(...)]
    function MyFunction {
  3. Nested function scope: Nested functions need individual suppressions; script-level suppressions don't cascade

See Also