Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: Test
on:
push:
branches: [ $default-branch ]
branches: [ main ]
pull_request:
workflow_dispatch:
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
Expand All @@ -23,3 +23,19 @@
$DebugPreference = 'Continue'
}
./build.ps1 -Task Test -Bootstrap
test_powershell:
name: Test
runs-on: windows-latest
strategy:
fail-fast: true
steps:
- uses: actions/checkout@v4
- name: Test
shell: powershell
env:
DEBUG: ${{ runner.debug == '1' }}
run: |
if($env:DEBUG -eq 'true' -or $env:DEBUG -eq '1') {
$DebugPreference = 'Continue'
}
./build.ps1 -Task Test -Bootstrap
Comment on lines +27 to +41

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 3 days ago

To fix the problem, explicitly declare the GITHUB_TOKEN permissions in the workflow using the permissions key, granting only the minimal scopes required. Since these jobs just check out code and run tests/build via build.ps1 and do not interact with issues, pull requests, or modify repository contents, they can typically operate with contents: read only.

The best way to fix this without changing existing functionality is to add a top-level permissions block to .github/workflows/test.yml (so it applies to all jobs) with contents: read. This documents the workflow’s intent and ensures least-privilege defaults if org or repo settings change later. Concretely, insert:

permissions:
  contents: read

between the on: block and the jobs: block (i.e., after line 6 and before line 7). No additional imports or methods are needed, as this is purely a YAML configuration change within the workflow file.

Suggested changeset 1
.github/workflows/test.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,6 +4,8 @@
     branches: [ main ]
   pull_request:
   workflow_dispatch:
+permissions:
+  contents: read
 jobs:
   test:
     name: Test
EOF
@@ -4,6 +4,8 @@
branches: [ main ]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
test:
name: Test
Copilot is powered by AI and may make mistakes. Always verify output.
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased

## [1.0.0-alpha1] 2026-03-22

### Breaking Changes

- **Minimum PowerShell version raised from 3.0 to 5.1.** PowerShellBuild 1.0.0+ requires
PowerShell 5.1 or later.
- **psake 5.0.0 required.** The consumer-facing `psakeFile.ps1` now uses psake 5.0.0
declarative task syntax with `Version 5` pinning.
- **`Invoke-psake` now returns `PsakeBuildResult`.** Build scripts that check
`$psake.build_success` should migrate to inspecting the returned object's `.Success` property.

### Added

- **Task caching** — Cacheable tasks (`StageFiles`, `Analyze`, `Pester`, `GenerateMarkdown`,
`GenerateMAML`, `GenerateUpdatableHelp`) now declare `Inputs`/`Outputs` for psake 5.0.0's
content-addressed caching. Unchanged tasks are automatically skipped on incremental builds.
Disable with `$PSBPreference.Build.EnableTaskCaching = $false`.
- **LLM-optimized test output** — New `$PSBPreference.Test.OutputMode` setting with values
`'Detailed'` (default, verbose human output), `'Minimal'` (failures only, compact), and
`'LLM'` (structured JSON with failure details, optimized for machine consumption).
- **External PesterConfiguration support** — New `$PSBPreference.Test.PesterConfigurationPath`
setting to load a `.psd1` file as the base PesterConfiguration. Explicit `$PSBPreference.Test`
values overlay on top.
- **`-Configuration` parameter on `Test-PSBuildPester`** — Pass a `[PesterConfiguration]` object
directly for full control over Pester execution.
- **`-OutputMode` parameter on `Test-PSBuildPester`** — Control output format per-invocation.
- **`-PesterConfigurationPath` parameter on `Test-PSBuildPester`** — Load external config files.
- **`Format-PSBuildResult`** — New public function to format psake 5.0.0's `PsakeBuildResult`
for Human, JSON, or GitHubActions output.
- **Declarative task syntax** — All tasks in `psakeFile.ps1` rewritten to use psake 5.0.0's
hashtable-based declarative syntax.
- **Invoke-Build parity** — `IB.tasks.ps1` updated with matching `Inputs`/`Outputs` caching
and new Pester parameter passthrough.

### Changed

- `$PSBPreference` now includes `Build.EnableTaskCaching`, `Test.OutputMode`, and
`Test.PesterConfigurationPath` keys.
- `Test-PSBuildPester` now always returns the Pester test result object for programmatic access.
- Synchronized inline `LocalizedData` in `PowerShellBuild.psm1` with all strings from
`en-US/Messages.psd1` (was missing signing-related strings from 0.8.0).

## [0.8.0] 2026-02-20

### Added
Expand Down
23 changes: 13 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

**PowerShellBuild** is a PowerShell module that provides a standardized set of build, test, and publish tasks for PowerShell module projects. It supports two popular PowerShell task-runner frameworks:

- **psake** (4.9.0+) — task-based build system
- **Invoke-Build** (5.8.1+) — alternative task runner
- **psake** (5.0.0+) — task-based build system with declarative syntax and content-addressed caching
- **Invoke-Build** (5.8.1+) — alternative task runner with native Inputs/Outputs caching

The module version is **0.7.3** and targets PowerShell 3.0+. It is cross-platform and tested on Windows, Linux, and macOS.
The module version is **1.0.0-alpha1** and targets PowerShell 5.1+. It is cross-platform and tested on Windows (including Windows PowerShell 5.1), Linux, and macOS.

---

Expand All @@ -26,8 +26,8 @@ PowerShellBuild/
├── Build/
│ └── Convert-PSAke.ps1 # Utility: converts psake tasks to Invoke-Build
├── PowerShellBuild/ # THE MODULE SOURCE (System Under Test)
│ ├── Public/ # Exported (public) functions — 9 functions
│ ├── Private/ # Internal functions — 1 function
│ ├── Public/ # Exported (public) functions — 13 functions
│ ├── Private/ # Internal functions — 2 functions
│ ├── en-US/
│ │ └── Messages.psd1 # Localized string resources
│ ├── PowerShellBuild.psd1 # Module manifest (version, deps, exports)
Expand Down Expand Up @@ -67,8 +67,8 @@ The hashtable is organized into sections:
| Section | Purpose |
|---------|---------|
| `General` | ProjectRoot, SrcRootDir, ModuleName, ModuleVersion, ModuleManifestPath |
| `Build` | OutDir, ModuleOutDir, CompileModule, CompileDirectories, CopyDirectories, Exclude |
| `Test` | Enabled, RootDir, OutputFile, OutputFormat, ScriptAnalysis, CodeCoverage, ImportModule, SkipRemainingOnFailure, OutputVerbosity |
| `Build` | OutDir, ModuleOutDir, CompileModule, CompileDirectories, CopyDirectories, Exclude, EnableTaskCaching |
| `Test` | Enabled, RootDir, OutputFile, OutputFormat, ScriptAnalysis, CodeCoverage, ImportModule, SkipRemainingOnFailure, OutputVerbosity, OutputMode, PesterConfigurationPath |
| `Help` | UpdatableHelpOutDir, DefaultLocale, ConvertReadMeToAboutHelp |
| `Docs` | RootDir, Overwrite, AlphabeticParamsOrder, ExcludeDontShow, UseFullTypeName |
| `Publish` | PSRepository, PSRepositoryApiKey, PSRepositoryCredential |
Expand Down Expand Up @@ -124,8 +124,11 @@ All functions reside in `PowerShellBuild/Public/`.
| `Test-PSBuildPester` | Runs Pester tests with configurable output and coverage |
| `Test-PSBuildScriptAnalysis` | Runs PSScriptAnalyzer with configurable severity threshold |
| `Publish-PSBuildModule` | Publishes the built module to a PowerShell repository |
| `Format-PSBuildResult` | Formats a PsakeBuildResult for Human, JSON, or GitHubActions output |

Private helper: `Remove-ExcludedItem` — filters file system items by regex patterns during builds.
Private helpers:
- `Remove-ExcludedItem` — filters file system items by regex patterns during builds
- `ConvertTo-PSBuildLLMOutput` — converts Pester results to structured JSON for LLM consumption

### Invoke-Build Alias

Expand Down Expand Up @@ -207,7 +210,7 @@ Defined in `requirements.psd1`, installed via **PSDepend**:
|--------|---------|
| BuildHelpers | 2.0.16 |
| Pester | ≥ 5.6.1 |
| psake | 4.9.0 |
| psake | 5.0.0 |
| PSScriptAnalyzer | 1.24.0 |
| InvokeBuild | 5.8.1 |
| platyPS | 0.14.2 |
Expand Down Expand Up @@ -386,7 +389,7 @@ After a successful build, output is in `Output/PowerShellBuild/<version>/`:
```
Output/
└── PowerShellBuild/
└── 0.7.3/
└── 1.0.0/
├── Public/ # (when CompileModule = $false)
├── Private/
├── en-US/
Expand Down
59 changes: 53 additions & 6 deletions PowerShellBuild/IB.tasks.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ Task Clean Init, {
}

# Synopsis: Builds module based on source directory
Task StageFiles Clean, {
Task StageFiles -Inputs {
Get-ChildItem -Path $PSBPreference.General.SrcRootDir -Recurse -File |
Where-Object { $_.Extension -in '.ps1', '.psm1', '.psd1', '.ps1xml', '.txt' }
} -Outputs {
if (Test-Path $PSBPreference.Build.ModuleOutDir) {
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File
}
} Clean, {
$buildParams = @{
Path = $PSBPreference.General.SrcRootDir
ModuleName = $PSBPreference.General.ModuleName
Expand Down Expand Up @@ -59,13 +66,19 @@ $analyzePreReqs = {
}

# Synopsis: Execute PSScriptAnalyzer tests
Task Analyze -If (. $analyzePreReqs) Build, {
Task Analyze -If (. $analyzePreReqs) -Inputs {
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -Include '*.ps1', '*.psm1', '*.psd1'
} -Outputs {
Join-Path $PSBPreference.Build.OutDir '.analyze-ok'
} Build, {
$analyzeParams = @{
Path = $PSBPreference.Build.ModuleOutDir
SeverityThreshold = $PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel
SettingsPath = $PSBPreference.Test.ScriptAnalysis.SettingsPath
}
Test-PSBuildScriptAnalysis @analyzeParams
# Write marker file for cache validation
Set-Content -Path (Join-Path $PSBPreference.Build.OutDir '.analyze-ok') -Value (Get-Date -Format 'o')
}

$pesterPreReqs = {
Expand All @@ -86,7 +99,13 @@ $pesterPreReqs = {
}

# Synopsis: Execute Pester tests
Task Pester -If (. $pesterPreReqs) Build, {
Task Pester -If (. $pesterPreReqs) -Inputs {
$testFiles = Get-ChildItem -Path $PSBPreference.Test.RootDir -Recurse -File -Filter '*.ps1'
$moduleFiles = Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -ErrorAction SilentlyContinue
@($testFiles) + @($moduleFiles)
} -Outputs {
$PSBPreference.Test.OutputFile
} Build, {
$pesterParams = @{
Path = $PSBPreference.Test.RootDir
ModuleName = $PSBPreference.General.ModuleName
Expand All @@ -101,6 +120,10 @@ Task Pester -If (. $pesterPreReqs) Build, {
ImportModule = $PSBPreference.Test.ImportModule
SkipRemainingOnFailure = $PSBPreference.Test.SkipRemainingOnFailure
OutputVerbosity = $PSBPreference.Test.OutputVerbosity
OutputMode = $PSBPreference.Test.OutputMode
}
if ($PSBPreference.Test.PesterConfigurationPath) {
$pesterParams.PesterConfigurationPath = $PSBPreference.Test.PesterConfigurationPath
}
Test-PSBuildPester @pesterParams
}
Expand All @@ -117,7 +140,15 @@ $genMarkdownPreReqs = {
}

# Synopsis: Generates PlatyPS markdown files from module help
Task GenerateMarkdown -if (. $genMarkdownPreReqs) StageFiles, {
Task GenerateMarkdown -if (. $genMarkdownPreReqs) -Inputs {
if (Test-Path $PSBPreference.Build.ModuleOutDir) {
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -Include '*.ps1', '*.psm1'
}
} -Outputs {
if (Test-Path $PSBPreference.Docs.RootDir) {
Get-ChildItem -Path $PSBPreference.Docs.RootDir -Recurse -File -Filter '*.md'
}
} StageFiles, {
$buildMDParams = @{
ModulePath = $PSBPreference.Build.ModuleOutDir
ModuleName = $PSBPreference.General.ModuleName
Expand All @@ -141,7 +172,15 @@ $genHelpFilesPreReqs = {
}

# Synopsis: Generates MAML-based help from PlatyPS markdown files
Task GenerateMAML -if (. $genHelpFilesPreReqs) GenerateMarkdown, {
Task GenerateMAML -if (. $genHelpFilesPreReqs) -Inputs {
if (Test-Path $PSBPreference.Docs.RootDir) {
Get-ChildItem -Path $PSBPreference.Docs.RootDir -Recurse -File -Filter '*.md'
}
} -Outputs {
if (Test-Path $PSBPreference.Build.ModuleOutDir) {
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -Filter '*-help.xml'
}
} GenerateMarkdown, {
Build-PSBuildMAMLHelp -Path $PSBPreference.Docs.RootDir -DestinationPath $PSBPreference.Build.ModuleOutDir
}

Expand All @@ -155,7 +194,15 @@ $genUpdatableHelpPreReqs = {
}

# Synopsis: Create updatable help .cab file based on PlatyPS markdown help
Task GenerateUpdatableHelp -if (. $genUpdatableHelpPreReqs) BuildHelp, {
Task GenerateUpdatableHelp -if (. $genUpdatableHelpPreReqs) -Inputs {
if (Test-Path $PSBPreference.Build.ModuleOutDir) {
Get-ChildItem -Path $PSBPreference.Build.ModuleOutDir -Recurse -File -Filter '*-help.xml'
}
} -Outputs {
if (Test-Path $PSBPreference.Help.UpdatableHelpOutDir) {
Get-ChildItem -Path $PSBPreference.Help.UpdatableHelpOutDir -Recurse -File -Filter '*.cab'
}
} BuildHelp, {
Build-PSBuildUpdatableHelp -DocsPath $PSBPreference.Docs.RootDir -OutputPath $PSBPreference.Help.UpdatableHelpOutDir
}

Expand Down
8 changes: 5 additions & 3 deletions PowerShellBuild/PowerShellBuild.psd1
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
@{
RootModule = 'PowerShellBuild.psm1'
ModuleVersion = '0.8.0'
ModuleVersion = '1.0.0'
GUID = '15431eb8-be2d-4154-b8ad-4cb68a488e3d'
Author = 'Brandon Olin'
CompanyName = 'Community'
Copyright = '(c) Brandon Olin. All rights reserved.'
Description = 'A common psake and Invoke-Build task module for PowerShell projects'
PowerShellVersion = '3.0'
PowerShellVersion = '5.1'
RequiredModules = @(
@{ModuleName = 'BuildHelpers'; ModuleVersion = '2.0.16' }
@{ModuleName = 'Pester'; ModuleVersion = '5.6.1' }
@{ModuleName = 'platyPS'; ModuleVersion = '0.14.1' }
@{ModuleName = 'psake'; ModuleVersion = '4.9.0' }
@{ModuleName = 'psake'; ModuleVersion = '5.0.0' }
)
FunctionsToExport = @(
'Build-PSBuildMAMLHelp'
Expand All @@ -26,12 +26,14 @@
'Publish-PSBuildModule'
'Test-PSBuildPester'
'Test-PSBuildScriptAnalysis'
'Format-PSBuildResult'
)
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @('*tasks')
PrivateData = @{
PSData = @{
Prerelease = 'alpha1'
Tags = @('psake', 'build', 'InvokeBuild')
LicenseUri = 'https://raw.githubusercontent.com/psake/PowerShellBuild/master/LICENSE'
ProjectUri = 'https://github.com/psake/PowerShellBuild'
Expand Down
17 changes: 17 additions & 0 deletions PowerShellBuild/PowerShellBuild.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ PSScriptAnalyzerResults=PSScriptAnalyzer results:
ScriptAnalyzerErrors=One or more ScriptAnalyzer errors were found!
ScriptAnalyzerWarnings=One or more ScriptAnalyzer warnings were found!
ScriptAnalyzerIssues=One or more ScriptAnalyzer issues were found!
NoCertificateFound=No valid code signing certificate was found. Verify the configured CertificateSource and that a certificate with a private key is available.
CertificateResolvedFromStore=Resolved code signing certificate from store [{0}]: Subject=[{1}]
CertificateResolvedFromThumbprint=Resolved code signing certificate by thumbprint [{0}]: Subject=[{1}]
CertificateResolvedFromEnvVar=Resolved code signing certificate from environment variable [{0}]
CertificateResolvedFromPfxFile=Resolved code signing certificate from PFX file [{0}]
SigningModuleFiles=Signing [{0}] file(s) matching [{1}] in [{2}]...
CreatingFileCatalog=Creating file catalog [{0}] (version {1})...
FileCatalogCreated=File catalog created: [{0}]
CertificateSourceAutoResolved=CertificateSource is 'Auto'. Resolved to '{0}'.
CertificateMissingPrivateKey=The resolved certificate does not have an accessible private key. Code signing requires a certificate with a private key. Subject=[{0}]
CertificateExpired=The resolved certificate has expired (NotAfter: {0}). Code signing requires a valid, unexpired certificate. Subject=[{1}]
CertificateMissingCodeSigningEku=The resolved certificate does not have the Code Signing Enhanced Key Usage (EKU: 1.3.6.1.5.5.7.3.3). Subject=[{0}]
CertificateSourceStoreNotSupported=CertificateSource 'Store' is only supported on Windows platforms.
LLMOutputHeader=Test results (structured output):
MinimalFailureLine=[FAIL] {0} ({1}:{2}) - {3}
PesterConfigLoaded=Loaded PesterConfiguration from [{0}]
InvalidPesterConfigPath=PesterConfiguration file [{0}] not found
'@
}
$importLocalizedDataSplat = @{
Expand Down
Loading
Loading