Skip to content
Merged
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
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
> example.

<p align="center">
<img src="media/psaketaskmodule-256x256.png" alt="Logo">

Check warning on line 28 in README.md

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (psaketaskmodule)
</p>

## Status - Work in progress
Expand Down Expand Up @@ -61,6 +61,7 @@
| Pester | Build | Run Pester tests |
| Test | Analyze, Pester | Run combined tests |
| Publish | Test | Publish module to defined PowerShell repository |
| Sign | SignCatalog | Sign module files and catalog (meta task) |

### Secondary Tasks

Expand All @@ -74,6 +75,9 @@
| GenerateMarkdown | StageFiles | Build markdown-based help |
| GenerateMAML | GenerateMarkdown | Build MAML help |
| GenerateUpdatableHelp | BuildHelp | Build updatable help cab |
| SignModule | Build | Authenticode-sign module files |
| BuildCatalog | SignModule | Build module catalog (.cat) file |
| SignCatalog | BuildCatalog | Authenticode-sign the catalog |

## Task customization

Expand All @@ -86,10 +90,10 @@
| Setting | Default value | Description |
|-------------------------------------------------------------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| $PSBPreference.General.ProjectRoot | `$env:BHProjectPath` | Root directory for the project |
| $PSBPreference.General.SrcRootDir | `$env:BHPSModulePath` | Root directory for the module |

Check warning on line 93 in README.md

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (BHPS) Suggestions: (baps, bops, bhp, BHP, bps)
| $PSBPreference.General.ModuleName | `$env:BHProjectName` | The name of the module. This should match the basename of the PSD1 file |
| $PSBPreference.General.ModuleVersion | `\<computed>` | The version of the module |
| $PSBPreference.General.ModuleManifestPath | `$env:BHPSModuleManifest` | Path to the module manifest (PSD1) |

Check warning on line 96 in README.md

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (BHPS) Suggestions: (baps, bops, bhp, BHP, bps)
| $PSBPreference.Build.OutDir | `$projectRoot/Output` | Output directory when building the module |
| $PSBPreference.Build.ModuleOutDir | `$outDir/$moduleName/$moduleVersion` | `For internal use only. Do not overwrite. Use '$PSBPreference.Build.OutDir' to set output directory` |
| $PSBPreference.Build.CompileModule | `$false` | Controls whether to "compile" module into single PSM1 or not |
Expand Down Expand Up @@ -120,11 +124,27 @@
| $PSBPreference.Help.ConvertReadMeToAboutHelp | `$false` | Convert project readme into the module about file |
| $PSBPreference.Docs.RootDir | `$projectRoot/docs` | Directory PlatyPS markdown documentation will be saved to. Other content in this directory, such as a README or an images folder, is left alone. |
| $PSBPreference.Docs.Overwrite | `$false` | Overwrite the markdown files in the docs folder using the comment based help as the source of truth. |
| $PSBPreference.Docs.ExcludeDontShow | `$false` | Exclude the parameters marked with `DontShow` in the parameter attribute from the help content. |

Check warning on line 127 in README.md

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (Dont) Suggestions: (dent, dint, doit, dolt, dona)
| $PSBPreference.Docs.UseFullTypeName | `$false` | Indicates that the target document will use a full type name instead of a short name for parameters. |
| $PSBPreference.Publish.PSRepository | `PSGallery` | PowerShell repository name to publish |
| $PSBPreference.Publish.PSRepositoryApiKey | `$env:PSGALLERY_API_KEY` | API key to authenticate to PowerShell repository with |

Check warning on line 130 in README.md

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (PSGALLERY) Suggestions: (psaltery, spaller, psaltry, psalter, psalters)
| $PSBPreference.Publish.PSRepositoryCredential | `$null` | Credential to authenticate to PowerShell repository with. Overrides `$psRepositoryApiKey` if defined |
| $PSBPreference.Sign.Enabled | `$false` | Enable/disable Authenticode signing of the built module. Must be `$true` for any of the signing or catalog tasks to run. |
| $PSBPreference.Sign.CertificateSource | `Auto` | How the code-signing certificate is resolved. Valid values are `Auto`, `Store`, `Thumbprint`, `EnvVar`, and `PfxFile`. See [Code signing](#code-signing). |
| $PSBPreference.Sign.CertStoreLocation | `Cert:\CurrentUser\My` | Windows certificate store path searched by the `Store` and `Thumbprint` certificate sources. |
| $PSBPreference.Sign.Thumbprint | `$null` | Thumbprint of the certificate to select from the store. Required by the `Thumbprint` certificate source and ignored by the others. |
| $PSBPreference.Sign.CertificateEnvVar | `SIGNCERTIFICATE` | Name of the environment variable holding the Base64-encoded PFX. Read by the `EnvVar` source, and used by `Auto` to detect whether a certificate is present. |

Check warning on line 136 in README.md

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (SIGNCERTIFICATE)
| $PSBPreference.Sign.CertificatePasswordEnvVar | `CERTIFICATEPASSWORD` | Name of the environment variable holding the password for the Base64-encoded PFX. Read by the `EnvVar` certificate source. |

Check warning on line 137 in README.md

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (CERTIFICATEPASSWORD)
| $PSBPreference.Sign.PfxFilePath | `$null` | File system path to a PFX/P12 certificate file. Required by the `PfxFile` certificate source. |
| $PSBPreference.Sign.PfxFilePassword | `$null` | Password for the PFX/P12 file as a `SecureString`. Used by the `PfxFile` certificate source. |
| $PSBPreference.Sign.Certificate | `$null` | A pre-resolved `X509Certificate2` object to sign with. When set, `CertificateSource` is ignored, which suits Azure Key Vault, an HSM, or another custom provider. |
| $PSBPreference.Sign.SkipCertificateValidation | `$false` | Skip the private key, expiration, and Code Signing EKU checks made on certificates loaded by the `EnvVar` and `PfxFile` sources. Not recommended in production. |
| $PSBPreference.Sign.TimestampServer | `http://timestamp.digicert.com` | RFC 3161 timestamp server URI embedded in the signature so that it stays valid after the certificate expires. |
| $PSBPreference.Sign.HashAlgorithm | `SHA256` | Authenticode hash algorithm. Valid values are `SHA256`, `SHA384`, `SHA512`, and `SHA1`. `SHA1` is deprecated. |
| $PSBPreference.Sign.FilesToSign | `@('*.psd1', '*.psm1', '*.ps1')` | Glob patterns of file names to sign, searched recursively under the module output directory. |
| $PSBPreference.Sign.Catalog.Enabled | `$false` | Enable/disable creation and signing of a Windows catalog (`.cat`) file. Also requires `$PSBPreference.Sign.Enabled` to be `$true`. |
| $PSBPreference.Sign.Catalog.Version | `2` | Catalog hash version. `1` is SHA1, compatible with Windows 7 and Windows Server 2008 R2. `2` is SHA2, required for Windows 8 and Windows Server 2012 and newer. |
| $PSBPreference.Sign.Catalog.FileName | `$null` | Name of the catalog file created in the module output directory. When `$null`, `<ModuleName>.cat` is used. |

## Modifying Task Dependencies

Expand All @@ -145,6 +165,46 @@
| $PSBGenerateMAMLDependency | 'GenerateMarkdown' | Tasks the 'GenerateMAML' task depends on. |
| $PSBGenerateUpdatableHelpDependency | 'BuildHelp' | Tasks the 'GenerateUpdatableHelp' task depends on. |
| $PSBPublishDependency | 'Test' | Tasks the 'Publish' task depends on. |
| $PSBSignModuleDependency | 'Build' | Tasks the 'SignModule' task depends on. |
| $PSBBuildCatalogDependency | 'SignModule' | Tasks the 'BuildCatalog' task depends on. |
| $PSBSignCatalogDependency | 'BuildCatalog' | Tasks the 'SignCatalog' task depends on. |
| $PSBSignDependency | 'SignCatalog' | Tasks the 'Sign' task depends on. |

## Code signing

PowerShellBuild can Authenticode-sign the staged module and wrap it in a Windows
catalog (`.cat`) file. The `SignModule`, `BuildCatalog`, `SignCatalog`, and
`Sign` tasks are opt-in: they skip with a warning unless
`$PSBPreference.Sign.Enabled` is `$true`, and the two catalog tasks additionally
require `$PSBPreference.Sign.Catalog.Enabled`. They also skip when
`Set-AuthenticodeSignature` or `New-FileCatalog` is unavailable, so a build that
enables signing still runs on Linux and macOS; it just does not sign there.

Where the code-signing certificate comes from is controlled by
`$PSBPreference.Sign.CertificateSource`:

- `Store` selects the first valid, unexpired code-signing certificate that has a
private key from `$PSBPreference.Sign.CertStoreLocation`.
- `Thumbprint` selects a specific certificate from that same store by
`$PSBPreference.Sign.Thumbprint`, which is what you want when more than one
code-signing certificate is installed.
- `EnvVar` decodes a Base64-encoded PFX from the environment variable named by
`$PSBPreference.Sign.CertificateEnvVar`, optionally decrypting it with the
password in the variable named by
`$PSBPreference.Sign.CertificatePasswordEnvVar`. This is the usual approach
for GitHub Actions, Azure Pipelines, and GitLab CI, where the certificate is
held as a masked secret.
- `PfxFile` loads a PFX/P12 file from `$PSBPreference.Sign.PfxFilePath` using
`$PSBPreference.Sign.PfxFilePassword`.
- `Auto`, the default, uses `EnvVar` when the certificate environment variable
is populated and falls back to `Store` when it is not. One build script can
therefore sign with the developer's own certificate locally and with the
pipeline secret in CI.

Setting `$PSBPreference.Sign.Certificate` to an already-resolved
`X509Certificate2` object bypasses all of the above, which is how to sign with a
certificate that comes from Azure Key Vault, a hardware security module, or
another custom provider.

## Examples

Expand Down
48 changes: 41 additions & 7 deletions tests/IBTasks.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

Describe 'Invoke-Build Tasks' {
BeforeAll {
$manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest

Check warning on line 26 in tests/IBTasks.tests.ps1

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (BHPS) Suggestions: (baps, bops, bhp, BHP, bps)
$outputDir = [IO.Path]::Combine($ENV:BHProjectPath, 'Output')
$outputModDir = [IO.Path]::Combine($outputDir, $env:BHProjectName)
$outputModVerDir = [IO.Path]::Combine($outputModDir, $manifest.ModuleVersion)
Expand All @@ -36,9 +36,9 @@
Test-Path $IBTasksFilePath | Should -Be $true
}

It 'Parseable by invoke-build' {

Check warning on line 39 in tests/IBTasks.tests.ps1

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (Parseable) Suggestions: (parsable, pauseable, parable, passable, pausable)
# Run IB in job to not pollute the environment
# Invoke-Build whatif still outputs in Appveyor in Pester even when directed to out-null. This doesn't happen locally. Redirecting all output to null

Check warning on line 41 in tests/IBTasks.tests.ps1

View workflow job for this annotation

GitHub Actions / CI / Run Linters

Unknown word (whatif) Suggestions: (waif, what, wharf, whats, what'd)
$IBTasksResult = Start-Job -ScriptBlock {
Invoke-Build -File $using:IBTasksFilePath -Whatif -Result IBTasksResult -ErrorAction Stop *>$null
$IBTasksResult
Expand Down Expand Up @@ -110,8 +110,9 @@
# psake/PowerShellBuild#105 there, and $PSBPreference.Build.Dependencies survived being
# replaced by the $PSB{TaskName}Dependency variables in #72, back in 0.7.0.
#
# Only the documented-to-defined direction is asserted. The reverse direction would fail
# today on the whole Sign section, which the README has never covered.
# Both directions are asserted. The reverse one -- every defined setting is documented --
# only became assertable once the sixteen $PSBPreference.Sign settings were added to the
# table; the whole Sign section had shipped in 0.8.0 with no entry in the README at all.

BeforeAll {
# Documented settings that are deliberately absent from the defaults. Both task files
Expand All @@ -124,22 +125,55 @@
'Build.CompileScriptHeader'
'Build.CompileScriptFooter'
)
}

It 'every setting the README table lists resolves against build.properties.ps1' {
# There is no matching allow-list for the reverse direction: every leaf of the defaults
# is currently in the table. Build.ModuleOutDir is the one setting consumers must not
# set, and it is documented anyway, with a description saying so -- which is the pattern
# to follow for anything else that turns out to be internal. Silencing a setting here
# instead would hide it from the very readers the table exists for.

function script:Get-PreferenceLeafPath {
# Walks the defaults and returns the dotted path of every leaf value. A nested
# hashtable (Test.ScriptAnalysis, Test.CodeCoverage, Sign.Catalog) is a container
# rather than a setting, so only the leaves beneath it are returned -- that is what
# the README table documents, one row per settable value.
param($Node, [string]$Prefix)

foreach ($name in $Node.Keys) {
$path = if ($Prefix) { "$Prefix.$name" } else { $name }
if ($Node[$name] -is [System.Collections.IDictionary]) {
Get-PreferenceLeafPath -Node $Node[$name] -Prefix $path
} else {
$path
}
}
}

$readMePath = [IO.Path]::Combine((Split-Path -Path $PSScriptRoot -Parent), 'README.md')
$documentedPath = [regex]::Matches(
$script:documentedPath = [regex]::Matches(
(Get-Content -Path $readMePath -Raw),
'(?m)^\|\s*\$PSBPreference((?:\.[A-Za-z_][A-Za-z0-9_]*)+)'
).ForEach({ $_.Groups[1].Value.TrimStart('.') }) | Sort-Object -Unique
}

$documentedPath | Should -Not -BeNullOrEmpty -Because 'the regex must still match the table'
It 'every setting the README table lists resolves against build.properties.ps1' {
$script:documentedPath | Should -Not -BeNullOrEmpty -Because 'the regex must still match the table'

$undefined = $documentedPath.Where({
$undefined = $script:documentedPath.Where({
$_ -notin $script:documentedWithoutDefault -and
-not (Test-PreferencePath -Root $script:defaultPreference -Segment ($_ -split '\.'))
})

$undefined -join ', ' | Should -BeNullOrEmpty
}

It 'every setting build.properties.ps1 defines is listed in the README table' {
$definedPath = Get-PreferenceLeafPath -Node $script:defaultPreference | Sort-Object -Unique

$definedPath | Should -Not -BeNullOrEmpty -Because 'the defaults must still be walkable'

$undocumented = $definedPath.Where({ $_ -notin $script:documentedPath })

$undocumented -join ', ' | Should -BeNullOrEmpty
}
}
Loading