These guidelines enforce readable, maintainable, and consistent PowerShell code.
- 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)
- 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
- Name lists/arrays in plural form.
✅ Examples:
$Users$InstalledApplications$ServerNames
- Start with
Is,Has, orCan.
✅ Examples:
$IsElevated$HasNetworkConnection$CanRetry
- 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-DeviceInventorySet-ClientConfigurationTest-NetworkPrerequisite
- Modules and functions must follow a clear namespace strategy.
- Module naming pattern is mandatory:
YFridelance.[PS|PWSH].<ModuleName>
- Examples:
YFridelance.PS.ClientManagementYFridelance.PWSH.Automation
- Exported function names must remain Verb-Noun and be scoped to the module domain (clear, non-generic naming).
- 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
)- Indentation: 4 spaces, no tabs.
- One statement per line.
- Use blank lines to separate logical blocks.
- Keep line length reasonable (guideline: ~120 chars).
- 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- Always consider performance, especially for large datasets.
- Prefer
foreach (...) {}over pipeline +ForEach-Objectwhen performance matters. - Use
Where-Objectcarefully and avoid unnecessary filtering in large pipelines.
- 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.
- On PowerShell 7+, prefer
ForEach-Object -Parallelfor suitable independent workloads. - On PowerShell 5.1, use classic
foreach(or explicit runspace jobs when justified). - Never use
-Parallelwithout 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
}
}
}$Volumes |
Where-Object { $_.DriveType -eq 'Fixed' } |
ForEach-Object {
# Processing
}- Wrap critical calls in
try/catch. - Provide meaningful error messages in
catch. - Empty
catchblocks are not allowed. - Use
-ErrorAction Stopwhere needed.
Example:
try {
$Volume = Get-Volume -DriveLetter C -ErrorAction Stop
}
catch {
Write-Error "Failed to read volume: $($_.Exception.Message)"
}- Use
Write-Hostsparingly. - Return objects for pipeline usage (
[PSCustomObject]or typed output). - Use
Write-Errorfor errors. - Use
Write-Verbosefor diagnostic output +[CmdletBinding()].
- 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)"
}
}❌ 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."
}- Variables: always PascalCase and descriptive.
- No one-letter variable names except rare local loop cases.
- Functions follow Verb-Noun.
- Modules must use namespace pattern
YFridelance.[PS|PWSH].<ModuleName>. - Use splatting from 4 parameters onward.
- Keep performance in mind: support both PS 5.1 and 7+; use PS7
ForEach-Object -Parallelwith version guard, otherwiseforeach (...) {}. - Empty
catchblocks are forbidden. - Ensure clean formatting and reliable error handling.
- Code and documentation are in English.
- Readability over brevity.