Skip to content

Latest commit

 

History

History
302 lines (244 loc) · 7.61 KB

File metadata and controls

302 lines (244 loc) · 7.61 KB

PowerShell Coding Guidelines

Goal

These guidelines enforce readable, maintainable, and consistent PowerShell code.


0) Language (mandatory)

  • Team communication can be in German, but code and documentation must be in English.
  • This includes:
    • comments
    • comment-based help
    • inline documentation
    • examples and error texts (where practical)

1) Variables

1.1 Naming

  • Variables must always start with an uppercase letter.
  • Variable names must be descriptive (no cryptic short names without context).
  • Names should reflect the variable's purpose or content.

✅ Good:

  • $Volume
  • $ComputerName
  • $UserProfilePath
  • $IsAdmin
  • $InstallResult

❌ Bad:

  • $v
  • $x1
  • $tmp
  • $data

1.2 Collections

  • Name lists/arrays in plural form.

✅ Examples:

  • $Users
  • $InstalledApplications
  • $ServerNames

1.3 Boolean variables

  • Start with Is, Has, or Can.

✅ Examples:

  • $IsElevated
  • $HasNetworkConnection
  • $CanRetry

2) Functions

  • Use Verb-Noun naming based on PowerShell conventions.
  • Use approved verbs only (Get-, Set-, New-, Remove-, Test-, Invoke-, ...).
  • Function names must clearly describe behavior.

✅ Examples:

  • Get-DeviceInventory
  • Set-ClientConfiguration
  • Test-NetworkPrerequisite

2.1 Namespace conventions (modules and functions)

  • Modules and functions must follow a clear namespace strategy.
  • Module naming pattern is mandatory:
    • YFridelance.[PS|PWSH].<ModuleName>
  • Examples:
    • YFridelance.PS.ClientManagement
    • YFridelance.PWSH.Automation
  • Exported function names must remain Verb-Noun and be scoped to the module domain (clear, non-generic naming).

3) Parameters

  • Parameter names must be descriptive and in PascalCase.
  • Use explicit parameter types where appropriate.
  • Required parameters must be marked with Mandatory = $true.
  • Optional parameters must be explicitly declared with Mandatory = $false.

Example:

param(
    [Parameter(Mandatory = $true)]
    [string]$ComputerName,

    [Parameter(Mandatory = $false)]
    [switch]$Force
)

4) Formatting

  • Indentation: 4 spaces, no tabs.
  • One statement per line.
  • Use blank lines to separate logical blocks.
  • Keep line length reasonable (guideline: ~120 chars).

5) Parameter passing with splatting

  • Use splatting when passing 4 or more parameters to a function/cmdlet.
  • This improves readability and maintainability.

Example:

$Parameters = @{
    ComputerName = $ComputerName
    Credential   = $Credential
    Force        = $Force
    ErrorAction  = 'Stop'
}

Set-ClientConfiguration @Parameters

6) Performance principles

  • Always consider performance, especially for large datasets.
  • Prefer foreach (...) {} over pipeline + ForEach-Object when performance matters.
  • Use Where-Object carefully and avoid unnecessary filtering in large pipelines.

6.1 PowerShell 5.1 and 7 compatibility

  • Default baseline is compatible with both Windows PowerShell 5.1 and PowerShell 7+.
  • Keep shared code paths 5.1-safe unless there is a clear performance/feature benefit.
  • If a PowerShell 7-only optimization is used, provide a fallback for 5.1.

6.2 Parallel processing strategy

  • On PowerShell 7+, prefer ForEach-Object -Parallel for suitable independent workloads.
  • On PowerShell 5.1, use classic foreach (or explicit runspace jobs when justified).
  • Never use -Parallel without a version guard.

✅ Preferred (cross-version safe):

if ($PSVersionTable.PSVersion.Major -ge 7) {
    $Volumes | ForEach-Object -Parallel {
        if ($_.DriveType -eq 'Fixed') {
            # Processing
        }
    }
}
else {
    foreach ($Volume in $Volumes) {
        if ($Volume.DriveType -eq 'Fixed') {
            # Processing
        }
    }
}

⚠️ Use with care:

$Volumes |
    Where-Object { $_.DriveType -eq 'Fixed' } |
    ForEach-Object {
        # Processing
    }

7) Error handling

  • Wrap critical calls in try/catch.
  • Provide meaningful error messages in catch.
  • Empty catch blocks are not allowed.
  • Use -ErrorAction Stop where needed.

Example:

try {
    $Volume = Get-Volume -DriveLetter C -ErrorAction Stop
}
catch {
    Write-Error "Failed to read volume: $($_.Exception.Message)"
}

8) Logging & output

  • Use Write-Host sparingly.
  • Return objects for pipeline usage ([PSCustomObject] or typed output).
  • Use Write-Error for errors.
  • Use Write-Verbose for diagnostic output + [CmdletBinding()].

9) Comments & documentation

  • Comment only where intent is not obvious.
  • Every public function must include comment-based help.
  • Every parameter must be documented (.PARAMETER ...).
  • Comment-based help must be inside the function body.
  • Every parameter set must have at least one .EXAMPLE.
  • Declare output type in the function ([OutputType(...)]).

Gold-standard template (2 parameter sets, 2 examples):

function Get-ClientStatus {
    [CmdletBinding(DefaultParameterSetName = 'ByComputerName')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'ByComputerName')]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByResourceId')]
        [ValidateRange(1, 2147483647)]
        [int]$ResourceId,

        [Parameter(Mandatory = $false)]
        [switch]$Force
    )

    <#
    .SYNOPSIS
    Returns client status for a device.

    .DESCRIPTION
    Resolves client status either by computer name or by resource ID.

    .PARAMETER ComputerName
    Target device name (ParameterSet: ByComputerName).

    .PARAMETER ResourceId
    Unique target resource ID (ParameterSet: ByResourceId).

    .PARAMETER Force
    Forces a fresh lookup and ignores cached data.

    .EXAMPLE
    Get-ClientStatus -ComputerName 'PC-001'
    Example for parameter set 'ByComputerName'.

    .EXAMPLE
    Get-ClientStatus -ResourceId 4711 -Force
    Example for parameter set 'ByResourceId'.
    #>

    try {
        switch ($PSCmdlet.ParameterSetName) {
            'ByComputerName' {
                [PSCustomObject]@{
                    ComputerName = $ComputerName
                    ResourceId   = $null
                    IsOnline     = $true
                    Source        = 'ComputerName'
                }
            }
            'ByResourceId' {
                [PSCustomObject]@{
                    ComputerName = 'Unknown'
                    ResourceId   = $ResourceId
                    IsOnline     = $true
                    Source        = 'ResourceId'
                }
            }
        }
    }
    catch {
        Write-Error "Failed to get client status: $($_.Exception.Message)"
    }
}

10) Bad vs good example

❌ Bad:

$v = Get-Volume C
if($v -ne $null){echo "ok"}

✅ Good:

$Volume = Get-Volume -DriveLetter C

if ($null -ne $Volume) {
    Write-Verbose "Volume successfully retrieved."
}

Team standard (short version)

  1. Variables: always PascalCase and descriptive.
  2. No one-letter variable names except rare local loop cases.
  3. Functions follow Verb-Noun.
  4. Modules must use namespace pattern YFridelance.[PS|PWSH].<ModuleName>.
  5. Use splatting from 4 parameters onward.
  6. Keep performance in mind: support both PS 5.1 and 7+; use PS7 ForEach-Object -Parallel with version guard, otherwise foreach (...) {}.
  7. Empty catch blocks are forbidden.
  8. Ensure clean formatting and reliable error handling.
  9. Code and documentation are in English.
  10. Readability over brevity.