All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
PowerShellBuild 1.0.0 is the first stable release. It closes a cycle of breaking changes that brings every core dependency to a current major version and fixes several gates that reported problems without enforcing them.
Read docs/migration-v0.8-to-v1.0.md before upgrading. It carries one entry per break with the migration steps for each, and a prompt you can hand to a coding agent to do the edits for you.
The breaks that can require action, shortest first:
| Change | Who it affects |
|---|---|
psake floor raised to 5.0.4 |
psake consumers on 4.x — the module will not import |
Pester floor raised to 6.0.0 |
consumers pinned to Pester 5.x — the module will not import |
PlatyPS moved to Microsoft.PowerShell.PlatyPS 1.x and is no longer installed for you |
anyone who builds help |
$PSBPreference.Docs.AlphabeticParamsOrder removed |
anyone who set it |
| Manifest now requires PowerShell 5.1 | anyone building on PowerShell 3.0 or 4.0 |
| Script analysis thresholds are enforced for the first time | anyone running the Analyze task — a build that passed may now correctly fail |
ParseError findings now count as errors |
anyone with a file that does not parse |
Three fixes are worth checking your build file for even though they cannot
break it, because each one means a setting you configured was doing nothing:
$PSBPreference.Test.ScriptAnalysisEnabled was never a real setting name,
code coverage percentages were truncated to zero so coverage gating could not
be used at all, and under Invoke-Build the coverage report was written in a
format the threshold gate could not read.
Everything below is the detail, one entry per issue.
-
#172 Breaking: the consumer Pester floor in
RequiredModulesis raised from5.6.1to6.0.0, andTest-PSBuildPesterrefuses to run under anything older. Pester 5.x is no longer supported. As with the psake floor, the lower minimum asserted support nothing verified — CI runs Pester 6 and nothing exercised a 5.x consumer. Pester 6 keeps the v5Should -Beassertions (Should.DisableV5defaults to$false), so most suites need no changes; the one behavioral change to watch isRun.FailOnNullOrEmptyForEach, which now defaults to$true. Also fixes a guard that could never work:Test-PSBuildPesterdeclared a5.0.0minimum while callingNew-PesterConfigurationandRun.SkipRemainingOnFailure, neither of which exists in 5.0.0. See the v0.8 → v1.0 migration guide. -
#166 Breaking: the consumer psake floor in
RequiredModulesis raised from4.9.0to5.0.4. psake 4.x is no longer supported.RequiredModulesis enforced at import, so a consumer with only psake 4.x installed cannot import the module at all. The lower floor was untested — CI has exercised only 5.0.4 since the toolchain moved there in #162 — so it asserted support nothing verified. Invoke-Build users are unaffected. See the v0.8 → v1.0 migration guide; most v4 build scripts work under v5 unchanged. -
#105 Breaking: help generation moved from
platyPS0.14.x toMicrosoft.PowerShell.PlatyPS1.x. PlatyPS is also no longer aRequiredModulesentry, soInstall-Module PowerShellBuildno longer installs it — the two PlatyPS modules cannot be loaded into one process, so forcing the new one into every session would break any consumer still holding the old one. Install it yourself if you build help.$PSBPreference.Docs.AlphabeticParamsOrderis removed, because PlatyPS 1.x always sorts parameters alphabetically and offers no way back. Generated markdown carries the 1.x schema, though its on-disk layout is unchanged, and now includes a module landing page that 0.14.x never produced. Updatable help is covered separately under Fixed. See the v0.8 → v1.0 migration guide. -
#144 Breaking:
Test-PSBuildScriptAnalysisnow counts PSScriptAnalyzerParseErrorrecords alongsideError. A file that does not parse at all previously satisfied no threshold — not even the strictest validated value,Information— so it was reported and the build passed anyway. It now fails every threshold exceptNone. See the v0.8 → v1.0 migration guide — a build that passed before may now correctly fail. -
#120 Breaking: the module manifest now requires PowerShell 5.1 or newer (
PowerShellVersion = '5.1', previously'3.0') and declaresCompatiblePSEditions = @('Desktop', 'Core'). The support floor is Windows PowerShell 5.1 or PowerShell 7.4+ (CI runs Windows PowerShell 5.1 and the runners' current PowerShell 7 release). See the v0.8 → v1.0 migration guide for details.
- #144
Test-PSBuildScriptAnalysisacceptsAnyas aSeverityThreshold, failing the build on any diagnostic record regardless of severity.Anywas already documented inbuild.properties.ps1but was missing from the parameter'sValidateSet, so setting$PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel = 'Any'failed parameter binding instead of working as documented.
-
#206
Build-PSBuildModule -Compileno longer compiles your working directory when no compile directories are given.CompileDirectoriesdefaulted to@(), andGet-ChildItem -Path @()treats an empty path as "not supplied" and falls back to the current location — so every.ps1beneath wherever the build ran was concatenated into the root module, which then failed to import while the build reported success. The parameter now defaults to@('Enum', 'Classes', 'Private', 'Public'), matching what the tasks already pass and what the README has always documented, and an explicitly empty list is guarded rather than expanded — that path was reachable through supported configuration by setting$PSBPreference.Build.CompileDirectories = @(). Compiling to an empty set now warns instead of silently producing a module with no functions. -
#207
$PSBPreference.Help.ConvertReadMeToAboutHelpworks when the output already has a culture directory. TheCopy-Itemthat writesabout_<Module>.help.txtsat inside theTest-Pathbranch that creates that directory, so an existing one meant no about help file was written at all — silently. It was reachable in compile mode whenever$PSBPreference.Build.CopyDirectoriesnamed the culture directory, which in compile mode is the only way to ship a locale directory at all. The-Forceon that copy was already there and unreachable; the guard now covers only the directory creation, as it does elsewhere in the module. -
#203 A publish that fails now fails the build.
Publish-Modulereports a failed publish as a non-terminating error — an unregistered repository, a rejected API key, a repository the credential cannot authenticate to — andPublish-PSBuildModulecalled it at the default preference, so the error was written to the error stream and the command returned normally. ThePublishtask in bothpsakeFile.ps1andIB.tasks.ps1then reported success for a module that was never published, which on a release workflow means a green build and no artifact in the gallery.Publish-Moduleis now called with-ErrorAction Stop, and an explicit-ErrorActionpassed toPublish-PSBuildModuleis forwarded so a consumer who wants the old behavior can still ask for it. If your publish has been quietly failing, this is the release where you find out. -
#201
$PSBPreference.Build.CompileModule = $truenow warns when the source.psm1callsExport-ModuleMember. Compiling appends the source root module after the concatenated function files, and compile mode copies noPublic/directory to the output, so the dot-sourcing loader that almost every module template generates discovers nothing and the appended call becomesExport-ModuleMember -Function @(). A module's effective exports are the intersection of that call andFunctionsToExport, so the built module exported nothing while the manifest correctly named every public function, and the build reported success. The only previous symptom wasNo commands have been exported. Skipping markdown generation.from a later task, which names a documentation problem rather than an empty module. The build still produces the same files; what changes is that it now tells you. Guard the call so it does nothing when the function directories are absent, or remove it and letFunctionsToExportgovern the export set. A mention ofExport-ModuleMemberin a comment does not trigger the warning. -
#98
$PSBPreference.Build.Excludeno longer aborts the build it is filtering. The exclusion used a labeledbreakaimed at the loop over its whole input, so a caller passing a collection lost every item after the first excluded one; on Windows PowerShell 5.1 thebreakescaped the function entirely and aborted whatever loop the caller was running, silently and with no error. Found while adding the coverage forBuild-PSBuildModulethat #98 asked for, which reaches the filter directly rather than one item at a time through the pipeline. -
#98
$PSBPreference.Build.CompileModule = $trueno longer produces a module with no functions in it when the build runs from a different drive than the module source. The compile loop read each function file through a path resolved relative to the current location, and a current location cannot always express another path relatively: on Windows PowerShell 5.1 a source on another drive resolves to.\C:\..., and a source outside the current PSDrive's root to a..\path that cannot climb past that root. Neither reads back, so every function was silently dropped and the compiled.psm1came out holding only its headers and footers -- and the build reported success. Files are now read through their full path. Also found while adding the #98 coverage; this repository's own Windows PowerShell 5.1 CI job reproduces it, with the checkout onD:and the test drive onC:. -
#193
$PSBPreference.Sign.SkipCertificateValidationnow does something. It was read only bypsakeFile.ps1, and even thereGet-PSBuildCertificateconsulted it only for theEnvVarandPfxFilesources — theStoreandThumbprintsources checked expiry and private key presence inside the filter that selects the certificate, where the switch could not reach them. So the documented escape hatch did nothing on two of the four sources for psake consumers, and nothing at all for Invoke-Build consumers, becauseIB.tasks.ps1never passed the setting toGet-PSBuildCertificatein either of its signing tasks. A build with an expired store certificate failed withNoCertificateFound, which points at a missing certificate rather than at the expiry that actually caused it.IB.tasks.ps1now passes the setting, and forStoreandThumbprintthe relaxation is a fallback rather than a blanket bypass: an unexpired certificate is still preferred whenever one exists, an expired one is selected only when no unexpired one was found, and a warning is emitted when that happens.Thumbprintstill matches the requested thumbprint, and both store-backed sources still require a private key, because that is part of how they select a certificate rather than a check layered on afterwards. Note thatEnvVarandPfxFileare different: the setting skips every check for them, the private key check included, so a certificate exported without its key is accepted here and fails later inSet-AuthenticodeSignatureinstead. -
#191 The README's psake and Invoke-Build examples assigned
$PSBPreference.Test.ScriptAnalysisEnabled, which is not a setting. The real one is nested,$PSBPreference.Test.ScriptAnalysis.Enabled, as the README's own settings table has always said.$PSBPreferenceis a plain hashtable, so the unrecognized name added a key nothing reads and neither errored nor warned. Check your own build file if you copied the psake example: it assigned$false, so script analysis has been running on every build even though you asked for it off. The Invoke-Build example assigned$true, which is already the default, so that one did nothing either way. Correcting the name is the whole fix; no module behavior changed. -
#185 Consumers on a non-English Windows PowerShell 5.1 host get the module's real messages.
PowerShellBuild.psm1carried a hand-maintained second copy of every string, bound wheneverImport-LocalizedDataresolved nothing, and it had drifted sixteen strings behinden-US/Messages.psd1— every certificate and signing message was absent, and the Pester floor was still reported as5.0.0. That lookup resolves by UI culture and the module shipsen-USonly: PowerShell 7 falls back toen-USand never saw the drift, but Windows PowerShell 5.1 does not fall back at all, so a French or Japanese install bound the stale copy. A missing string does not throw, so the symptom was a blankWARNING:line, or a bareScriptHaltedwhere the signing tasks meant to say no certificate was found. The copy is gone,en-USis now requested by name when the culture lookup misses, and an import that cannot find the strings at all fails outright rather than blanking every message.The same sweep found one string the module read and never shipped:
Publish-PSBuildModulevalidated-PathagainstPathDoesNotExist, whichen-US/Messages.psd1did not define, so passing a path that does not exist failed with a blank message on every host and culture while the very next check in the same validation block reported itself properly. The string is now defined, and a test asserts that every string the module reads is one it ships. -
#124 The docs tree can hold documentation that is not generated help. A
README.mdat its root, aCONTRIBUTING.mdbeside the generated markdown, animages/orguides/directory alongside the locale — none of it interferes with the help tasks any more. Markdown without PlatyPS metadata used to fail the build outright; the move to PlatyPS 1.x ended that, because the markdown and MAML steps now select documents by type rather than taking every*.mdin the directory. What remained wasBuild-PSBuildUpdatableHelptreating every subdirectory of the docs tree as a help locale and warning, once per directory, about a module landing page that was never going to be there. It now considers only directories that are actually locales. -
#178 Invoke-Build consumers get the code coverage output format they configured.
IB.tasks.ps1read$PSBPreference.Test.CodeCoverage.OutputFormat, but the setting isOutputFileFormat, so the expression evaluated to$nulland bound to the[string]parameter as an empty string rather than falling back to itsJaCoCodefault. Pester accepted the empty format silently, leaving the documented setting inert and the coverage threshold gate unable to find or parse a report. The psake task was unaffected. -
#169
Build-PSBuildUpdatableHelpproduces a help cabinet. It never could before: it needed a module landing page thatBuild-PSBuildMarkdowndid not generate, passed an undefined variable as the cabinet source folder, and was never given the module name — so any build that reached theGenerateUpdatableHelptask failed with a parameter-binding error.Build-PSBuildMarkdownnow writes the landing page, and the task passes the module name and output path it always should have. Using the task requires aHelpInfoUriin your module manifest; without one it warns and produces nothing, rather than writing a cabinet with noHelpInfo.xmlto find it by. See the v0.8 → v1.0 migration guide. -
#147
Test-PSBuildScriptAnalysisretries the analysis when a PSScriptAnalyzer rule crashes on an internal race (PSScriptAnalyzer#1538), which is unrelated to the code being analyzed and succeeds on a re-run. Consumers who set$ErrorActionPreference = 'Stop'— common in a build script — previously got a randomly red build. A crash that survives every attempt is still surfaced, so a persistent failure behaves as it did before. -
#96
Test-PSBuildScriptAnalysisnow fails the build when PSScriptAnalyzer reports findings at or above the configured severity threshold. The severity counts were computed from$_Severity— an undefined variable — instead of$_.Severity, so all three counts were always zero and theError,Warning, andInformationthresholds could never fail a build. Script analysis reported its findings and the build passed regardless. Because the defaultFailBuildOnSeverityLevelisError, any consumer running theAnalyzetask had a gate that only ever looked like it was working. See the v0.8 → v1.0 migration guide — a build that passed before may now correctly fail. -
#96
Test-PSBuildScriptAnalysisno longer fails with a path-resolution error whenSettingsPathis not supplied. An unsupplied path was forwarded to PSScriptAnalyzer as-Settings '', which resolved against the current directory and threw before any analysis ran, so the function's own documented example could not run as written. -
#102
Test-PSBuildPesterno longer raises a parameter-binding error from its cleanup logic when the optionalModuleNameparameter is not supplied. -
#102
Test-PSBuildPesternow respects a Pester module that is already loaded in the session instead of unconditionally importing the newest installed version on top of it, which crashed with a Pester.dll version conflict when two Pester versions were installed side by side. When no Pester is loaded, the newest installed version (5.0.0 minimum) is imported as before, and a loaded Pester older than 5.0.0 now produces a clear error. -
#138
Test-PSBuildPesternow reports real code coverage percentages and compares them againstCodeCoverageThresholdcorrectly. Each percentage was passed through[Math]::Truncate, which collapses any fraction to zero, so the coverage report always printed0.00%(or100.00%) and every threshold above zero failed the build unless coverage was exactly 100%. Consumers who set$PSBPreference.Test.CodeCoverage.Thresholdcould not use coverage gating at all. The comparison is now strictly more permissive than before, so a build that passed with a coverage threshold set still passes.
- #133
Test-PSBuildPesternow fails the build when a Pester run fails for any reason, not only when individual tests fail. Previously the function gated only onFailedCount, so aBeforeAll/AfterAllthat threw or a test file that errored during discovery left the count at zero and the build passed despite tests never running. The gate now checks the run's aggregateResultproperty, which Pester derives from all failure categories. Companion to #128, which fixes the same gap in this repository's own build file.
- Restore Windows PowerShell 5.1 (Desktop edition) compatibility, which regressed
in 0.8.0.
Get-PSBuildCertificateused the PowerShell 7+-only ternary operator, causing the file to fail to parse and the whole module to fail to import under Windows PowerShell 5.1 — even though the manifest still declares support for it. The ternary is replaced with anif/elseexpression, and the$IsWindowsplatform guard now treats the absent automatic variable on Desktop edition as Windows (matching the existing pattern inBuild-PSBuildUpdatableHelp). Behavior on PowerShell 7+ is unchanged.
- #92 Add Authenticode
code-signing support for PowerShell modules with three new public functions:
Get-PSBuildCertificate- Resolves code-signing X509Certificate2 objects from certificate store, PFX files, Base64-encoded environment variables, or pre-resolved certificate objectsInvoke-PSBuildModuleSigning- Signs PowerShell module files (*.psd1, *.psm1, *.ps1) with Authenticode signatures supporting configurable timestamp servers and hash algorithmsNew-PSBuildFileCatalog- Creates Windows catalog (.cat) files for tamper detection
- New build tasks for module signing pipeline:
SignModule,BuildCatalog,SignCatalog,Sign(meta-task) - Extended
$PSBPreference.Signconfiguration section with certificate source selection, timestamp server configuration, hash algorithm options, and catalog generation settings
- Remove extra backticks during localization text migration.
- Add new dependencies variables to allow end user to modify which tasks are run.
- Add localization support.
- The
$PSBPreferencevariable now supports the following PlatyPSNew-MarkdownHelpandUpdate-MarkdownHelpboolean options:$PSBPreference.Docs.AlphabeticParamsOrder$PSBPreference.Docs.ExcludeDontShow$PSBPreference.Docs.UseFullTypeName
- The
$PSBPreferencevariable now supports the following Pester test configuration options:$PSBPreference.Test.SkipRemainingOnFailurecan be set to None, Run, Container and Block. The default value is None.$PSBPreference.Test.OutputVerbositycan be set to None, Normal, Detailed, and Diagnostic. The default value is Detailed.
- Fix a bug in
Build-PSBuildMarkdownwhere a hashtable item was added twice.
- #71 Compiled modules are now explicitly created as UTF-8 files.
- #67 You can now
overwrite existing markdown files using
$PSBPreference.Docs.Overwriteand setting it to$true. - #72 Loosen
dependencies by allowing them to be overwritten with
$PSBPreference.TaskDependencies.
- Bump Pester to latest 5.6.1
- #52 Pester object wasn't being passed back after running tests, causing the Pester task to never fail (via @webtroter)
- #55 Add
-Moduleparameter toBuild-PSBuildUpdatableHelp(via @IMJLA) - #60 Fix Windows
PowerShell compatibility in
Initialize-PSBuild(via @joshooaj) - #62 Fix code coverage output fle format not working (via @OpsM0nkey)
- Fixed bug in IB task
GenerateMarkdownwhen dot sourcing precondition
- #50 Invoke-Build tasks brought inline with psake equivalents (via @JustinGrote)
- New code coverage parameters for setting output path and format:
$PSBPreference.Test.CodeCoverage.OutputFile- Output file path for code coverage results$PSBPreference.Test.CodeCoverage.OutputFileFormat- Code coverage output format
-
When "compiling" a monolithic PSM1, add support for both inserting headers/footers for the entire PSM1, and for each script file. Control these via the following new build parameters (via @pauby)
$PSBPreference.Build.CompileHeader$PSBPreference.Build.CompileFooter$PSBPreference.Build.CompileScriptHeader$PSBPreference.Build.CompileScriptFooter
-
Add ability to import project module from output directory prior to executing Pester tests. Toggle this with
$PSBPreference.Test.ImportModule. Defaults to$false. (via @joeypiccola) -
Use
$PSBPreference.Build.CompileDirectoriesto control directories who's contents will be concatenated into the PSM1 when$PSBPreference.Build.CompileModuleis$true. Defaults to@('Enum', 'Classes', 'Private', 'Public'). -
Use
$PSBPreference.Build.CopyDirectoriesto control directories that will be copied "as is" into the built module. Default is an empty array.
-
$PSBPreference.Build.Excludenow should be a list of regex expressions when$PSBPreference.Build.CompileModuleis$false(default). -
Use Pester v5
-
Overriding
$PSBPreference.Build.OutDirnow correctly determines the final module output directory.$PSBPreference.Build.ModuleOutDiris now computed internally and SHOULD NOT BE SET DIRECTLY.$PSBPreference.Build.OutDirwill accept both relative and fully-qualified paths. -
Before, when
$PSBPreference.Build.CompileModulewas set to$true, any files listed in$PSBPreference.Build.Excludeweren't being excluded like they should have been. Now, when it is$true, files matching regex expressions in$PSBPreference.Build.Excludewill be properly excluded (via @pauby) -
$PSBPreference.Help.DefaultLocalenow defaults toen-USon Linux since it is not correctly determined withGet-UICulture.
- Allow using both
CredentialandApiKeywhen publishing a module (via @pauby)
- Don't overwrite Pester parameters when specifying
OutputPathorOutputFormat(via @ChrisLGardner)
- Don't create module page MD file.
-
Breaking: Refactor build properties into a single hashtable
$PSBPreference -
#11 The Invoke-Build tasks are now auto-generated from the psake tasks via a converter script (via @JustinGrote)
-
#19 Allow the
BHBuildOutputenvironment variable defined byBuildHelpersto be set via the$PSBPreference.Build.ModuleOutDirproperty of the build tasks (via @pauby)
- Add
Publishtask to publish the module to the defined PowerShell Repository (PSGallery by default).
- #4 Fix syntax for
Analyzetask inIB.tasks.ps1(via @nightroman)
- Initial commit