PowerShell scripts and modules
July 10, 2026 ยท View on GitHub
Deploy, drift, dependency, and bootstrap PowerShell. Conventions match
the existing repo style. Reference doc:
Docs/Deploy/Scripts.md.
Style and structure
- PowerShell 7.2+ is the minimum (declared in
Sentinel.Common.psd1). Usepwsh-only features freely; don't worry about Windows PowerShell 5.1 compatibility. Set-StrictMode -Version Latestis set in shared modules. Watch for read-of-undefined-property ($x.PSObject.Properties['Foo']is the safe pattern when$xmight not haveFoo).$ErrorActionPreference = 'Stop'at the top of every script and module. Errors should fail loud, not be silently swallowed.
File header (required for new files)
#
# Sentinel-As-Code/Deploy/<Name>.ps1
#
# Created by <author> on DD/MM/YYYY.
#
<#
.SYNOPSIS
One-line summary.
.DESCRIPTION
Multi-paragraph description: what does this script do, when
should I run it, what does it produce?
.PARAMETER ParamName
Per-parameter description.
.EXAMPLE
./Deploy/Foo.ps1 -ParamName Value
Brief description of what the example does.
.NOTES
Author: <author>
Version: <semver>
Last Updated: YYYY-MM-DD
Repository: Sentinel-As-Code
Requires: PowerShell 7.2+, Az.Accounts (etc.)
#>
Use the Sentinel.Common module
The repo's shared module exports the patterns every deployer uses:
| Function | Purpose |
|---|---|
Write-PipelineMessage -Level Section|Info|Success|Warning|Error -Message ... | Single source of truth for ADO/GitHub/local logging output |
Invoke-SentinelApi -Uri ... -Method ... -Headers ... | REST wrapper with retry-on-transient + StreamReader response-body recovery |
Connect-AzureEnvironment -ResourceGroup ... -Workspace ... -Region ... [-IsGov] [-PlaybookResourceGroup ...] | Az context bootstrap; returns a state hashtable the caller assigns to its own scope |
Get-ContentDependencies -Path <yaml/json> -KnownFunctions <hashtable> | Discover dependencies for a single content file |
Get-KqlBareIdentifiers -Query <kql> | Extract bare table/function references from a KQL query |
Get-KqlWatchlistReferences / Get-KqlExternalDataReferences / Get-ContentKqlQuery / Remove-KqlComments | Lower-level KQL discovery helpers |
Import the module at the top of any new deployer-style script:
Import-Module "$PSScriptRoot/../Modules/Sentinel.Common/Sentinel.Common.psd1" -Force
Hard rules
- Don't reimplement
Write-PipelineMessage,Invoke-SentinelApi, orConnect-AzureEnvironment. The Sentinel.Common module extracted those out of every script into the shared module specifically because inline duplication caused bug-fix-in-one-copy regressions. If a shared helper doesn't fit your need, add a new export toSentinel.Common.psm1(with a Pester test) rather than inlining. - PSGallery module pins. Every workflow / pipeline pins
powershell-yamlandPesterto specific versions (env varsYAML_VERSION/PESTER_VERSION). New scripts that need a PSGallery dep should pin too. [void]Boolean-leaking calls.Dictionary.Remove(key),HashSet.Add(item),List.Remove(item)all returnBooleanthat PowerShell pipes to the function output stream. Prefix with[void]to suppress:[void]$dict.Remove($key). TheSet-PlaybookPermissions.ps1fix is the canonical example.- Single-element array indexing.
($func | ...)[0]may index into a string when the pipeline returns one item. Use@(...)[0]to force array context first. - Strict-mode-safe property access. Don't use
$obj.MaybePresentdirectly when strict mode is on; use$obj.PSObject.Properties['MaybePresent']and check for$null. - Return values, not script-scope mutation.
Connect-AzureEnvironmentused to mutate$script:*in the caller; that pattern doesn't survive module extraction ($script:in a module refers to the module's scope, not the caller's). Return a hashtable; let the caller assign.
Adding a new function to Sentinel.Common
- Add the function definition under the appropriate section in
Modules/Sentinel.Common/Sentinel.Common.psm1. - Add it to
Export-ModuleMember -Function ...at the bottom of the.psm1. - Add it to
FunctionsToExportinModules/Sentinel.Common/Sentinel.Common.psd1. - Bump
ModuleVersionin the.psd1(semver: patch for bug fix, minor for new function, major for breaking change). - Update
ReleaseNotesinPrivateData.PSData. - Add Pester tests to
Tests/Test-SentinelCommon.Tests.ps1.
Testing
Every public function gets a Pester unit test. See
./pester-tests.instructions.md
for the AST-extraction pattern used to test functions defined in
scripts (rather than modules).
Cross-references
- Script reference:
Docs/Deploy/Scripts.md - Module manifest:
Modules/Sentinel.Common/Sentinel.Common.psd1 - Module body:
Modules/Sentinel.Common/Sentinel.Common.psm1 - Tests:
Tests/Test-SentinelCommon.Tests.ps1