Version Constraints#
A version constraint says which versions of a dependency are acceptable. PowerShell offers several places to state one — an Install-PSResource call, a #Requires line, a module manifest, a .csproj — and they do not all take the same string. Rather than learn a different dialect for each, express every constraint in one notation: the NuGet version range. It is the native syntax of the install tooling (PSResourceGet) and of .NET package references, it follows SemVer, and it makes intent — a floor, a ceiling, an exact pin, a range — explicit and reviewable.
This page is the PowerShell implementation of the Dependencies standard: the locking spectrum and the update-velocity-versus-supply-chain-risk balance are argued there; this page is how you express each point of that spectrum in PowerShell. It sits under the PowerShell standard, builds on Security → Supply chain, and pairs with Dependency Updates, which keeps pins current.
The rule#
- State every module or package version constraint as a NuGet version range, in explicit interval brackets —
[6.0.0, ),[6.0.0, 7.0.0),[6.0.0]— never a bare number. - Never write a bare version as a constraint. A bare
6.0.0means different things on different surfaces — a minimum to .NET package references, but the exact required version to PSResourceGet (see the bare-version trap) — so the same string silently changes meaning. Brackets say one thing everywhere. - Choose the lock deliberately — exact, patch, minor, major, or latest — from the locking spectrum; tighter is safer but slower to update, looser is faster but less vetted. The Dependencies standard argues the balance.
- Declare a compatibility floor in a module (
ModuleVersion,[6.0.0, )) so you do not over-constrain your consumers; pin tight what an application or CI installs for reproducibility, and let the updater move it. Reserve the exact pin for strict reproducibility, not as a way to avoid updates. - Keep pins current with automated update pull requests — see Dependency Updates.
NuGet version-range syntax#
The interval notation, from the NuGet reference:
| Range | Meaning | Notes |
|---|---|---|
[6.0.0, ) |
x ≥ 6.0.0 |
minimum, inclusive — the default |
(6.0.0, ) |
x > 6.0.0 |
minimum, exclusive |
[6.0.0] |
x = 6.0.0 |
exact pin |
(, 6.0.0] |
x ≤ 6.0.0 |
maximum, inclusive |
(, 6.0.0) |
x < 6.0.0 |
maximum, exclusive |
[6.0.0, 7.0.0) |
6.0.0 ≤ x < 7.0.0 |
major lock — any 6.x |
[6.0.0, 7.0.0] |
6.0.0 ≤ x ≤ 7.0.0 |
closed range |
(6.0.0, 7.0.0) |
6.0.0 < x < 7.0.0 |
open range |
A square bracket [ ] includes the endpoint; a parenthesis ( ) excludes it. Follow the NuGet guidance on ceilings: do not cap a dependency you do not own unless you know of an incompatibility — an unnecessary upper bound holds consumers back from fixes — but do bound a dependency whose next major would break you. A test framework such as Pester is the typical case.
The locking spectrum in PowerShell#
PowerShell can express every point of the locking spectrum. The module GUID is the identity pin — orthogonal to the version, it binds to the exact module and combines with any row below; the rest set version tightness, tightest first:
| Lock | NuGet range (PSResourceGet, PackageReference) |
#Requires / RequiredModules |
|---|---|---|
| Identity | not a version — pins which module | add GUID = '<module-guid>' to any row |
| Exact | [6.1.2] |
RequiredVersion = '6.1.2' |
Patch (any 6.1.x) |
[6.1.0, 6.2.0) |
ModuleVersion = '6.1.0'; MaximumVersion = '6.1.*' |
Minor (any 6.x) |
[6.0.0, 7.0.0) |
ModuleVersion = '6.0.0'; MaximumVersion = '6.*' |
Major (floor, ≥ 6.0.0) |
[6.0.0, ) |
ModuleVersion = '6.0.0' |
| Latest | omit -Version; * in PackageReference |
a bare name, @{ ModuleName = 'Pester' } |
- Identity + exact is the tightest — the pin never drifts; the bot proposes each re-pin and a human reviews it. Pair the
GUIDwithRequiredVersion, or with a lockfile-resolved install, for the strongest supply-chain posture. - Patch and minor are the everyday tracks: let fixes (and, for minor, additive features) flow while a new major is held back.
- Major — a floor with no ceiling — accepts breaking releases; use it where you actively co-evolve with the dependency, and expect the updater to route those as human-reviewed
update:majorpull requests. - Latest pulls whatever is newest, unvetted and non-reproducible — avoid it for anything shipped or run in CI (see Dependencies → the balance).
Where the range applies directly#
Installing and restoring — PSResourceGet#
Install-PSResource, Find-PSResource, Save-PSResource, and Update-PSResource take the range on -Version, as does the version field of a -RequiredResource hashtable or a -RequiredResourceFile manifest:
# any 6.x — the standard shape for a tool that breaks across majors
Install-PSResource -Name Pester -Version '[6.0.0, 7.0.0)' -TrustRepository
# floor only — highest available >= 8.0.0
Install-PSResource -Name Az -Version '[8.0.0, )' -TrustRepository
A restore manifest states the same range per resource:
@{
Pester = @{ version = '[6.0.0, 7.0.0)'; repository = 'PSGallery' }
PSScriptAnalyzer = @{ version = '[1.22.0, )'; repository = 'PSGallery' }
}
The bare-version trap#
PSResourceGet diverges from NuGet on one point: a bare version is treated as the exact required version, not a minimum. Install-PSResource -Name Pester -Version '6.0.0' installs only 6.0.0 — it will not accept 6.0.1. To get a minimum you must write the range, -Version '[6.0.0, )'. This is exactly why the rule forbids bare versions: always use brackets, and the constraint reads the same on every surface.
Building — .NET package references#
A binary module, or a build or test project, references NuGet packages with the same range syntax in PackageReference:
<PackageReference Include="YamlDotNet" Version="[16.0.0, 17.0.0)" />
Here a bare Version="16.0.0" is a minimum — NuGet's own semantics — the mirror image of the PSResourceGet trap: the very same string means "minimum" to the build and "exact" to the installer. Brackets remove the ambiguity.
#Requires and module manifests#
#Requires -Modules and a manifest's RequiredModules do not accept a NuGet range string. They take a module-specification hashtable whose keys are typed: ModuleVersion (minimum) and RequiredVersion (exact) parse as [version], while MaximumVersion is a string that also accepts the N.* wildcard. The spectrum table gives the keys for each lock, and Module Requirements is the full reference with an executable proof; the rules that constrain them:
RequiredVersioncannot be combined withModuleVersionorMaximumVersion. A range needs the floor and ceiling keys together; the exact pin stands alone.MaximumVersionis inclusive and understands theN.*wildcard, so a major lock isMaximumVersion = '6.*'— every6.x, nothing in7— with no6.999.999sentinel.- The module
GUIDpins identity, orthogonal to the version: a supply-chain control, not part of the version constraint (see Security → Supply chain). Add it to any lock. - The
#Requires -Versionstatement and a manifest'sPowerShellVersionare not package ranges — each is a single minimum engine version (we target PowerShell 7). Leave them as a bare version:#Requires -Version 7.0.
Not a version range — pin by digest#
Ranges are for packages resolved from a gallery. Dependencies that are not gallery packages are pinned differently: GitHub Actions and container images pin to an immutable commit SHA or image digest — an identity pin — never a moving tag or a range. See Security → Supply chain and GitHub Actions.