Skip to content

Add Authenticode signing support for PowerShell modules - #92

Merged
HeyItsGilbert merged 10 commits into
mainfrom
claude/add-module-signing-XKMbq
Feb 20, 2026
Merged

Add Authenticode signing support for PowerShell modules#92
HeyItsGilbert merged 10 commits into
mainfrom
claude/add-module-signing-XKMbq

Conversation

@HeyItsGilbert

Copy link
Copy Markdown
Member

Summary

This PR adds comprehensive Authenticode code-signing capabilities to PowerShellBuild, enabling modules to be signed with digital certificates from multiple sources. It includes three new public functions and corresponding build tasks for signing module files and creating/signing Windows catalog files.

Key Changes

  • New Function: Get-PSBuildCertificate - Resolves code-signing X509Certificate2 objects from five different sources:

    • Auto (environment variable or certificate store, configurable)
    • Windows certificate store (with optional thumbprint filtering)
    • Base64-encoded PFX from environment variables (CI/CD pipelines)
    • PFX files on disk with optional password protection
    • Pre-resolved certificate objects (for custom providers like Azure Key Vault)
  • New Function: Invoke-PSBuildModuleSigning - Signs PowerShell module files (*.psd1, *.psm1, *.ps1) with Authenticode signatures, supporting configurable timestamp servers and hash algorithms (SHA256, SHA384, SHA512, SHA1)

  • New Function: New-PSBuildFileCatalog - Creates Windows catalog (.cat) files that record cryptographic hashes of module contents for tamper detection

  • New Build Tasks - Added to both psakeFile.ps1 and IB.tasks.ps1:

    • SignModule - Signs module files with Authenticode
    • BuildCatalog - Creates a Windows catalog file
    • SignCatalog - Signs the catalog file
    • Sign - Meta-task that orchestrates the full signing pipeline
  • Configuration - Extended build.properties.ps1 with comprehensive Sign configuration section supporting:

    • Certificate source selection and parameters
    • Timestamp server configuration
    • Hash algorithm selection
    • File inclusion patterns
    • Catalog generation settings (version, filename)
  • Localization - Added localized messages for certificate resolution, file signing, and catalog creation

Implementation Details

  • All signing operations include platform checks (Windows-only) with appropriate warnings
  • Pre-condition checks ensure signing is only attempted when enabled and dependencies are available
  • Certificate resolution supports both explicit configuration and environment-based auto-detection
  • Task dependencies ensure proper execution order: Build → SignModule → BuildCatalog → SignCatalog
  • Verbose logging throughout for troubleshooting certificate resolution and signing operations

https://claude.ai/code/session_01Bt5Xb9HLoSppQ22PQUTyGP

Introduces a flexible, opt-in code-signing pipeline covering:
- Authenticode signing of module files (*.psd1, *.psm1, *.ps1)
- Windows catalog (.cat) file creation via New-FileCatalog
- Authenticode signing of the catalog file
**New public functions:**
- Get-PSBuildCertificate – resolves a code-signing cert from five
interchangeable sources: Auto, Store, Thumbprint, EnvVar (Base64 PFX
in a CI/CD secret), or PfxFile. Auto mode checks the env var first
and falls back to the certificate store, enabling the same psakeFile
to work on developer machines and in GitHub Actions / Azure DevOps.
- Invoke-PSBuildModuleSigning – signs files matching configurable glob
patterns under the module output directory using Set-AuthenticodeSignature.
- New-PSBuildFileCatalog – wraps New-FileCatalog to create a
SHA2 (version 2) or SHA1 (version 1) catalog file.
**New psake/Invoke-Build tasks:**
- SignModule – signs module files; skips gracefully when disabled or
on non-Windows platforms.
- BuildCatalog – creates the .cat file; independent enable flag.
- SignCatalog – signs the catalog file.
- Sign – meta task (depends on SignCatalog).
All tasks respect overridable $PSB*Dependency variables so consumers
can splice them into any position in their build graph, e.g.:
$PSBPublishDependency = @('Sign')
**New $PSBPreference.Sign properties:**
Enabled, CertificateSource, CertStoreLocation, Thumbprint,
CertificateEnvVar, CertificatePasswordEnvVar, PfxFilePath,
PfxFilePassword, Certificate (direct X509Certificate2 object),
TimestampServer, HashAlgorithm, FilesToSign,
Catalog.{Enabled, Version, FileName}
All features are disabled by default (Sign.Enabled = $false).
https://claude.ai/code/session_01Bt5Xb9HLoSppQ22PQUTyGP

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Authenticode signing support to the PowerShellBuild pipeline by introducing certificate resolution + module/catalog signing functions and wiring them into both psake and Invoke-Build task graphs.

Changes:

  • Adds new public functions for certificate resolution, module file signing, and Windows catalog generation.
  • Extends build configuration and localized messages to support signing options.
  • Introduces new build tasks (SignModule/BuildCatalog/SignCatalog/Sign) in both psake and Invoke-Build task files.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 16 comments.

Show a summary per file
FileDescription
PowerShellBuild/psakeFile.ps1Adds signing/catalog tasks and dependency defaults to the psake build pipeline.
PowerShellBuild/IB.tasks.ps1Adds equivalent signing/catalog tasks for Invoke-Build.
PowerShellBuild/build.properties.ps1Introduces Sign configuration (cert sources, timestamp, algorithms, catalog settings).
PowerShellBuild/en-US/Messages.psd1Adds localized strings for certificate resolution, signing, and catalog creation.
PowerShellBuild/Public/Get-PSBuildCertificate.ps1New public function to resolve certificates from Store/Thumbprint/EnvVar/PfxFile.
PowerShellBuild/Public/Invoke-PSBuildModuleSigning.ps1New public function to sign module files using Set-AuthenticodeSignature.
PowerShellBuild/Public/New-PSBuildFileCatalog.ps1New public function to generate a Windows catalog file via New-FileCatalog.
PowerShellBuild/PowerShellBuild.psd1Exports the newly added public functions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadPowerShellBuild/Public/New-PSBuildFileCatalog.ps1 Outdated
Comment threadPowerShellBuild/Public/Invoke-PSBuildModuleSigning.ps1
Comment threadPowerShellBuild/Public/Invoke-PSBuildModuleSigning.ps1
Comment threadPowerShellBuild/IB.tasks.ps1 Outdated
Comment on lines +245 to +267
Task SignModule -Depends $PSBSignModuleDependency -PreCondition $signModulePreReqs {
$certParams = @{
CertificateSource = $PSBPreference.Sign.CertificateSource
CertStoreLocation = $PSBPreference.Sign.CertStoreLocation
CertificateEnvVar = $PSBPreference.Sign.CertificateEnvVar
CertificatePasswordEnvVar = $PSBPreference.Sign.CertificatePasswordEnvVar
}
if ($PSBPreference.Sign.Thumbprint) {
$certParams.Thumbprint = $PSBPreference.Sign.Thumbprint
}
if ($PSBPreference.Sign.PfxFilePath) {
$certParams.PfxFilePath = $PSBPreference.Sign.PfxFilePath
}
if ($PSBPreference.Sign.PfxFilePassword) {
$certParams.PfxFilePassword = $PSBPreference.Sign.PfxFilePassword
}

$certificate = if ($PSBPreference.Sign.Certificate) {
$PSBPreference.Sign.Certificate
} else {
Get-PSBuildCertificate @certParams
}

CopilotAIFeb 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The certificate-resolution block is duplicated across SignModule and SignCatalog tasks (and also repeated in IB.tasks.ps1). This increases the risk of future drift (e.g., adding a new cert source/parameter in one place but not the other). Consider factoring this into a shared helper (e.g., Resolve-PSBuildSigningCertificate) used by both tasks.

Suggested change
Task SignModule -Depends $PSBSignModuleDependency-PreCondition $signModulePreReqs {
$certParams=@{
CertificateSource =$PSBPreference.Sign.CertificateSource
CertStoreLocation =$PSBPreference.Sign.CertStoreLocation
CertificateEnvVar =$PSBPreference.Sign.CertificateEnvVar
CertificatePasswordEnvVar =$PSBPreference.Sign.CertificatePasswordEnvVar
}
if ($PSBPreference.Sign.Thumbprint) {
$certParams.Thumbprint=$PSBPreference.Sign.Thumbprint
}
if ($PSBPreference.Sign.PfxFilePath) {
$certParams.PfxFilePath=$PSBPreference.Sign.PfxFilePath
}
if ($PSBPreference.Sign.PfxFilePassword) {
$certParams.PfxFilePassword=$PSBPreference.Sign.PfxFilePassword
}
$certificate=if ($PSBPreference.Sign.Certificate) {
$PSBPreference.Sign.Certificate
} else {
Get-PSBuildCertificate@certParams
}
functionResolve-PSBuildSigningCertificate {
param(
[Parameter(Mandatory=$true)]
$SignPreference
)
$certParams=@{
CertificateSource =$SignPreference.CertificateSource
CertStoreLocation =$SignPreference.CertStoreLocation
CertificateEnvVar =$SignPreference.CertificateEnvVar
CertificatePasswordEnvVar =$SignPreference.CertificatePasswordEnvVar
}
if ($SignPreference.Thumbprint) {
$certParams.Thumbprint=$SignPreference.Thumbprint
}
if ($SignPreference.PfxFilePath) {
$certParams.PfxFilePath=$SignPreference.PfxFilePath
}
if ($SignPreference.PfxFilePassword) {
$certParams.PfxFilePassword=$SignPreference.PfxFilePassword
}
if ($SignPreference.Certificate) {
return$SignPreference.Certificate
}
returnGet-PSBuildCertificate@certParams
}
Task SignModule -Depends $PSBSignModuleDependency-PreCondition $signModulePreReqs {
$certificate=Resolve-PSBuildSigningCertificate-SignPreference $PSBPreference.Sign

Copilot uses AI. Check for mistakes.
Comment threadPowerShellBuild/psakeFile.ps1 Outdated
Comment threadPowerShellBuild/IB.tasks.ps1 Outdated
Comment threadPowerShellBuild/Public/Get-PSBuildCertificate.ps1 Outdated
Comment threadPowerShellBuild/Public/Invoke-PSBuildModuleSigning.ps1
Comment threadPowerShellBuild/Public/New-PSBuildFileCatalog.ps1
* Introduced tests for `Get-PSBuildCertificate`, `Invoke-PSBuildModuleSigning`, and `New-PSBuildFileCatalog`.
* Validated various certificate sourcing methods including `Auto`, `Store`, `Thumbprint`, `EnvVar`, and `PfxFile`.
* Ensured proper parameter validation and help documentation for each function.
* Established integration tests to verify the workflow of signing modules and creating catalogs.
- Introduced `-SkipValidation` parameter to bypass validation checks for certificates from `EnvVar` or `PfxFile` sources.
- Enhanced error handling for missing or invalid certificates.
- Updated localization strings for better clarity on certificate validation messages.
…Get-PSBuildCertificate`
* Implemented a check to throw an error if the 'Store' certificate source is used on non-Windows platforms.
* Added localized message for unsupported certificate source on non-Windows.
* Updated tests to handle the new error action for better verbosity control.
* Removed tests that check for the existence and help documentation of `Get-PSBuildCertificate`.
* Updated context descriptions to clarify platform-specific behavior.
* Ensured tests are appropriately skipped on non-Windows platforms.
* Updated tests to skip execution on non-Windows systems.
* Ensures that Store mode tests are only run in a compatible environment.
- Introduced new test files `Invoke-PSBuildModuleSigning.tests.ps1` and `New-PSBuildFileCatalog.tests.ps1` to cover the functionality of code signing.
- Removed the old `Signing.tests.ps1` file to streamline test organization.
- Ensured tests validate the existence, parameters, and expected behaviors of the `Invoke-PSBuildModuleSigning` and `New-PSBuildFileCatalog` functions.
- Added checks for help documentation and parameter validation, including mandatory parameters and default values.
…uild tasks
* Added verbose output for certificate resolution in `Get-PSBuildCertificate`.
* Introduced `SkipCertificateValidation` option in `build.properties.ps1` for CI environments.
* Enhanced `psakeFile.ps1` to support verbose logging across various build tasks.
* Improved readability and maintainability of the build script.
…t-PSBuildCertificate`
* Simplified test cases by removing the default Auto mode test.
* Adjusted the remaining tests for clarity and consistency.
…n `Get-PSBuildCertificate`
* Introduced a test to verify that the function defaults to Auto mode when no `CertificateSource` is specified.
* Enhanced the existing Store mode test to check for verbose output indicating the resolution to 'Store'.
@HeyItsGilbert
HeyItsGilbert merged commit a8a877d into mainFeb 20, 2026
5 checks passed
@HeyItsGilbert
HeyItsGilbert deleted the claude/add-module-signing-XKMbq branch February 20, 2026 07:04
@HeyItsGilbertHeyItsGilbert mentioned this pull request Feb 21, 2026
HeyItsGilbert added a commit that referenced this pull request Feb 21, 2026
### Added
- [**#92**](#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 objects
- `Invoke-PSBuildModuleSigning` - Signs PowerShell module files (*.psd1,
*.psm1, *.ps1) with Authenticode signatures supporting configurable
timestamp servers and hash algorithms
- `New-PSBuildFileCatalog` - Creates Windows catalog (.cat) files for
tamper detection
- New build tasks for module signing pipeline: `SignModule`,
`BuildCatalog`,
`SignCatalog`, `Sign` (meta-task)
- Extended `$PSBPreference.Sign` configuration section with certificate
source selection, timestamp server configuration, hash algorithm
options,
and catalog generation settings
### Fixed
- Remove extra backticks during localization text migration.
tablackburn added a commit that referenced this pull request Jun 4, 2026
## Summary
PowerShellBuild 0.8.0 fails to **import** under Windows PowerShell 5.1
(Desktop). `Get-PSBuildCertificate` (added in #92) used the PowerShell
7+-only ternary operator; since the module loader dot-sources every
`Public/*.ps1` at import, the parse failure broke import of the whole
module on 5.1 — even though the manifest still declares
`PowerShellVersion = '3.0'`.
## Fix
- **Ternary → `if/else` expression** in `Get-PSBuildCertificate`
(5.1-safe; identical PS7 behavior).
- **`$IsWindows` guard made 5.1-safe** using the repo's existing idiom
(`$null -ne $IsWindows -and -not $IsWindows`, as in
`Build-PSBuildUpdatableHelp`). On Desktop edition the automatic variable
is absent and the platform is always Windows.
## Audit
Swept all `.ps1/.psm1/.psd1` under `PowerShellBuild/`, `build/`, and
`tests/` (and re-parsed every module file on a real 5.1 engine) for
PS7-only constructs (ternary, `??`/`??=`, `?.`/`?[ ]`, `&&`/`||`,
`clean{}`, `-Parallel`, Core-only `$Is*` vars, Core-only cmdlets/types).
The two issues in `Get-PSBuildCertificate.ps1` were the only real
incompatibilities.
## Guardrails
- **PSScriptAnalyzer compatibility rules**
(`PSUseCompatibleSyntax`/`Commands`/`Types`) enabled in
`ScriptAnalyzerSettings.psd1`, targeting Windows PowerShell 5.1
(Desktop) and PowerShell 7. `PSUseCompatibleSyntax` checks the target
language version regardless of the engine it runs on, so it catches a
ternary even from `pwsh`. Two documented false positives are suppressed
with justifications (`Get-ChildItem -CodeSigningCert` provider dynamic
parameter; `Invoke-Pester -Configuration` from the required Pester 5+).
- **`Analyze` build task fails on any compatibility violation** (these
rules are Warning-severity, which the existing Error-only gate would not
catch).
- **New `import-smoke` CI job** parses and imports the module on the
real Windows PowerShell 5.1 engine (`shell: powershell`).
## Verification
- Parse + dot-source + `Import-Module` on **real Windows PowerShell
5.1**: clean (original failed with the documented parse errors).
- PSScriptAnalyzer with the new settings: 0 compatibility findings;
confirmed it flags a reintroduced ternary.
- psake `Test` (Analyze + Pester) on pwsh 7: Analyze clean of
compatibility issues; **304 Pester tests pass**, including all of
`Get-PSBuildCertificate.tests.ps1`.
## Support contract
Per maintainer direction, the manifest
(`PowerShellVersion`/`CompatiblePSEditions`) is **left unchanged** in
this PR; the code is fixed to comply with the existing 5.1/Desktop
contract. Formalizing the declared floor is tracked for the v1.0.0
roadmap (#120).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@HeyItsGilbert@claude