microsoft/hve-core

Public

mirrored from https://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
70a0307cefe332cb3473de97d6fbd4a6ade5bb6e

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/instructions/coding-standards/powershell/powershell.instructions.md

377lines · modecode

1---
2description: "PowerShell scripting conventions"
3applyTo: '**/*.ps1, **/*.psm1, **/*.psd1'
4---
5
6# PowerShell Script Instructions
7
8These instructions define conventions for authoring PowerShell scripts, modules, and data files in this repository. Apply these conventions to `.ps1` scripts, `.psm1` modules, and `.psd1` data files.
9
10## Copyright Headers
11
12Every PowerShell file requires a copyright header containing two lines:
13
14```powershell
15# Copyright (c) Microsoft Corporation.
16# SPDX-License-Identifier: MIT
17```
18
19Placement varies by file type:
20
21```powershell
22# Script (.ps1): after shebang, before #Requires
23#!/usr/bin/env pwsh
24# Copyright (c) Microsoft Corporation.
25# SPDX-License-Identifier: MIT
26#Requires -Version 7.0
27
28# Module (.psm1): first lines (no shebang)
29# Copyright (c) Microsoft Corporation.
30# SPDX-License-Identifier: MIT
31
32# Test file (.Tests.ps1): after #Requires -Modules Pester
33#Requires -Modules Pester
34# Copyright (c) Microsoft Corporation.
35# SPDX-License-Identifier: MIT
36
37# Data file (.psd1): first lines (no shebang)
38# Copyright (c) Microsoft Corporation.
39# SPDX-License-Identifier: MIT
40```
41
42CI validates copyright headers through the repository's copyright validation script, if one is configured. Check `package.json` for a copyright validation command.
43
44## Script Structure
45
46Production scripts follow a 10-section structure. Each section appears in the order documented below.
47
48### Shebang
49
50`#!/usr/bin/env pwsh` is required on all `.ps1` files for cross-platform portability. Do not include a shebang on `.psm1` or `.psd1` files.
51
52### Requires Statements
53
54`#Requires -Version 7.0` is required on all scripts and modules. Place it after the copyright header. In modules, place the `#Requires` statement after the copyright header and purpose comment.
55
56### Comment-Based Help
57
58Use block comment style with `.SYNOPSIS`, `.DESCRIPTION`, `.PARAMETER`, `.EXAMPLE`, and `.NOTES` sections:
59
60```powershell
61<#
62.SYNOPSIS
63 Brief one-line description.
64.DESCRIPTION
65 Detailed description of what the script does.
66.PARAMETER RepoRoot
67 Root directory of the repository.
68.EXAMPLE
69 ./Invoke-ScriptName.ps1 -RepoRoot /repo
70.NOTES
71 Runs via: npm run script-name
72#>
73```
74
75### CmdletBinding and Parameters
76
77`[CmdletBinding()]` with a typed `param()` block is required on all scripts. Declare parameter types, defaults, and `Mandatory` attributes explicitly:
78
79```powershell
80[CmdletBinding()]
81param(
82 [Parameter(Mandatory = $false)]
83 [string]$RepoRoot = (git rev-parse --show-toplevel 2>$null) ?? $PSScriptRoot,
84
85 [Parameter(Mandatory = $false)]
86 [string]$OutputPath = (Join-Path $RepoRoot 'logs/results.json')
87)
88```
89
90### Error Preference
91
92Set `$ErrorActionPreference = 'Stop'` immediately after the param block. This ensures unhandled errors terminate execution.
93
94### Module Imports
95
96Import module dependencies using the `Join-Path` pattern with `-Force` to ensure fresh imports:
97
98```powershell
99Import-Module (Join-Path $PSScriptRoot 'Modules/Helpers.psm1') -Force
100```
101
102### Region Blocks
103
104Use `#region`/`#endregion` with descriptive labels to group logical sections:
105
106```powershell
107#region Functions
108# ... function definitions
109#endregion Functions
110
111#region Main Execution
112# ... entry point logic
113#endregion Main Execution
114```
115
116### Main Execution Guard
117
118Wrap main execution in an invocation guard that enables dot-sourcing for test files. This pattern ensures main execution only runs when the script is invoked directly, not when dot-sourced by Pester tests:
119
120```powershell
121if ($MyInvocation.InvocationName -ne '.') {
122 # Main execution logic
123}
124```
125
126## Module Structure
127
128Module files (`.psm1`) follow a distinct pattern from scripts:
129
130* No shebang line
131* Purpose comment after the copyright header: `# ModuleName.psm1` and `# Purpose: ...`
132* `#Requires -Version 7.0` after the purpose comment
133* Functions with full comment-based help, `[CmdletBinding()]`, and `[OutputType()]`
134* Explicit `Export-ModuleMember -Function @(...)` at the end of the file
135
136For class-only modules that expose no standalone functions, use `Export-ModuleMember -Function @()`. Consumers import class-only modules with `using module` for type availability.
137
138```powershell
139# Classes.psm1
140class ValidationResult {
141 [string]$Name
142 [bool]$Passed
143}
144
145Export-ModuleMember -Function @()
146```
147
148Consumer usage:
149
150```powershell
151using module './Classes.psm1'
152```
153
154## Naming Conventions
155
156| Element | Convention | Example |
157|------------|----------------------|-------------------------------|
158| Functions | Verb-Noun PascalCase | `Get-ValidationResult` |
159| Scripts | Verb-Noun PascalCase | `Invoke-PSScriptAnalyzer.ps1` |
160| Parameters | PascalCase with type | `[string]$OutputPath` |
161| Variables | PascalCase | `$ResultList` |
162| Modules | PascalCase | `CIHelpers.psm1` |
163
164## Error Handling
165
166Set `$ErrorActionPreference = 'Stop'` at the script level. Use try-catch blocks in the main execution guard with explicit exit codes.
167
168Error action preferences for different contexts:
169
170* `Write-Error -ErrorAction Continue` for non-fatal errors in catch blocks
171* `-ErrorAction SilentlyContinue` for optional command checks (e.g., testing if a command exists)
172* `-ErrorAction Stop` for critical operations that must succeed
173* `$LASTEXITCODE` checks after external commands (e.g., `git`, `npm`)
174* `throw` for validation failures within functions
175
176```powershell
177if ($MyInvocation.InvocationName -ne '.') {
178 try {
179 $result = Invoke-CoreFunction -RepoRoot $RepoRoot
180 $result | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath -Encoding UTF8
181 exit 0
182 }
183 catch {
184 Write-Error -ErrorAction Continue "ScriptName failed: $($_.Exception.Message)"
185 exit 1
186 }
187}
188```
189
190## Output and Logging
191
192### Console Output
193
194`Write-Host` with `-ForegroundColor` and emoji prefixes provides visual feedback during local development. `Write-Host` is allowed in this codebase (PSAvoidUsingWriteHost is excluded from PSScriptAnalyzer rules).
195
196```powershell
197Write-Host "✅ Validation passed: $count files clean" -ForegroundColor Green
198Write-Host "⚠️ Warning: $skipped files skipped" -ForegroundColor Yellow
199Write-Host "❌ Validation failed: $errors errors found" -ForegroundColor Red
200```
201
202### CI Integration
203
204The CI output API from `scripts/lib/Modules/CIHelpers.psm1` provides platform-abstracted functions:
205
206* `Write-CIAnnotation` for CI annotations (GitHub Actions `::warning::`, Azure DevOps `##vso[task.logissue]`, local `Write-Warning`)
207* `Set-CIOutput` for step output variables
208* `Write-CIStepSummary` for markdown step summaries
209* `Set-CIEnv` for persistent CI environment variables
210
211```powershell
212Import-Module (Join-Path $PSScriptRoot '../lib/Modules/CIHelpers.psm1') -Force
213Write-CIAnnotation -Level 'Warning' -Message 'Deprecated API usage detected' -File $filePath -Line $lineNum
214```
215
216### JSON Results
217
218Write structured output to the `logs/` directory for downstream consumption. Use `ConvertTo-Json` with sufficient depth and UTF8 encoding:
219
220```powershell
221$result | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath -Encoding UTF8
222```
223
224## Parameter Validation
225
226Apply validation attributes to enforce parameter constraints:
227
228* `[ValidateNotNullOrEmpty()]` for required string parameters that must contain a value
229* `[ValidateScript()]` for custom validation logic with scriptblock predicates
230* `[ValidateSet()]` for parameters constrained to a fixed set of values
231
232```powershell
233param(
234 [Parameter(Mandatory = $true)]
235 [ValidateNotNullOrEmpty()]
236 [string]$RepoRoot,
237
238 [Parameter(Mandatory = $false)]
239 [ValidateSet('Error', 'Warning', 'Information')]
240 [string]$Severity = 'Error'
241)
242```
243
244## PSScriptAnalyzer Compliance
245
246Use the repository's PSScriptAnalyzer configuration file (typically a `.psd1` file) for analysis. Check `package.json` for a PowerShell linting command, or run `Invoke-ScriptAnalyzer` directly with the configuration file path.
247
248Key enforced rules:
249
250* Approved verbs for function names (`PSUseApprovedVerbs`)
251* Block comment-based help before function body (`PSProvideCommentHelp`)
252* `[OutputType()]` attribute on functions (`PSUseOutputTypeCorrectly`)
253* Full cmdlet names, no aliases (`PSAvoidUsingCmdletAliases`)
254* Compatible syntax targeting PowerShell 5.1, 7.0, and 7.2 (`PSUseCompatibleSyntax`)
255
256Allowed exceptions:
257
258* `Write-Host` is permitted (PSAvoidUsingWriteHost excluded)
259* Positional parameters are allowed (PSAvoidUsingPositionalParameters disabled)
260* Singular nouns are allowed (PSUseSingularNouns disabled)
261
262## Complete Script Example
263
264<!-- <template-complete-script> -->
265```powershell
266#!/usr/bin/env pwsh
267# Copyright (c) Microsoft Corporation.
268# SPDX-License-Identifier: MIT
269#Requires -Version 7.0
270
271<#
272.SYNOPSIS
273 Brief one-line description.
274.DESCRIPTION
275 Detailed description of what the script does.
276.PARAMETER RepoRoot
277 Root directory of the repository.
278.PARAMETER OutputPath
279 Path for the JSON results file.
280.EXAMPLE
281 ./Invoke-ScriptName.ps1 -RepoRoot /repo -OutputPath logs/results.json
282.NOTES
283 Runs via: npm run script-name
284#>
285
286[CmdletBinding()]
287param(
288 [Parameter(Mandatory = $false)]
289 [string]$RepoRoot = (git rev-parse --show-toplevel 2>$null) ?? $PSScriptRoot,
290
291 [Parameter(Mandatory = $false)]
292 [string]$OutputPath = (Join-Path $RepoRoot 'logs/results.json')
293)
294
295$ErrorActionPreference = 'Stop'
296
297Import-Module (Join-Path $PSScriptRoot 'Modules/Helpers.psm1') -Force
298
299#region Functions
300
301function Invoke-CoreFunction {
302 <#
303 .SYNOPSIS
304 Core logic for the script.
305 .OUTPUTS
306 [hashtable] Results object.
307 #>
308 [CmdletBinding()]
309 [OutputType([hashtable])]
310 param(
311 [Parameter(Mandatory = $true)]
312 [ValidateNotNullOrEmpty()]
313 [string]$RepoRoot
314 )
315
316 # Implementation
317 return @{ Status = 'Pass'; Issues = @() }
318}
319
320#endregion Functions
321
322#region Main Execution
323
324if ($MyInvocation.InvocationName -ne '.') {
325 try {
326 $result = Invoke-CoreFunction -RepoRoot $RepoRoot
327 $result | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath -Encoding UTF8
328 exit 0
329 }
330 catch {
331 Write-CIAnnotation -Level 'Error' -Message $_.Exception.Message
332 Write-Error -ErrorAction Continue "ScriptName failed: $($_.Exception.Message)"
333 exit 1
334 }
335}
336
337#endregion Main Execution
338```
339<!-- </template-complete-script> -->
340
341## Complete Module Example
342
343<!-- <template-complete-module> -->
344```powershell
345# Copyright (c) Microsoft Corporation.
346# SPDX-License-Identifier: MIT
347
348# HelperModule.psm1
349# Purpose: Shared utility functions for area operations.
350
351#Requires -Version 7.0
352
353function Get-SomeData {
354 <#
355 .SYNOPSIS
356 Retrieves structured data from source.
357 .PARAMETER Path
358 File path to read.
359 .OUTPUTS
360 [hashtable] Parsed data object.
361 #>
362 [CmdletBinding()]
363 [OutputType([hashtable])]
364 param(
365 [Parameter(Mandatory = $true)]
366 [ValidateNotNullOrEmpty()]
367 [string]$Path
368 )
369
370 # Implementation
371}
372
373Export-ModuleMember -Function @(
374 'Get-SomeData'
375)
376```
377<!-- </template-complete-module> -->
378