Browse Source

Merge pull request #98 from raandree/ai/release-5.0.0

ci: publish releases from CI on a version tag, starting with 5.0.0-rc1
pull/99/head 5.0.0-rc1
Raimund Andrée 1 week ago
committed by GitHub
parent
commit
e0f5366a92
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 119
      .github/scripts/Get-ReleaseInfo.ps1
  2. 148
      .github/scripts/New-ModulePackage.ps1
  3. 124
      .github/workflows/ci.yml
  4. 44
      .memory-bank/activeContext.md
  5. 19
      .memory-bank/decisions/0010-one-version.md
  6. 39
      .memory-bank/decisions/0012-ci-releases.md
  7. 41
      .memory-bank/progress.md
  8. 11
      .memory-bank/systemPatterns.md
  9. 33
      .memory-bank/techContext.md
  10. 19
      CHANGELOG.md
  11. 3
      Docs/Contributing.md
  12. 2
      Docs/Contributing/02-Writing.md
  13. 110
      Docs/Contributing/05-Releasing.md
  14. 3
      Docs/README.md
  15. 9
      NTFSSecurity/NTFSSecurity.psd1
  16. 10
      Tests/Manifest.Tests.ps1
  17. 255
      Tests/Release.Tests.ps1

119
.github/scripts/Get-ReleaseInfo.ps1

@ -0,0 +1,119 @@
<#
.SYNOPSIS
Returns the version of the module and its release notes from CHANGELOG.md.
.DESCRIPTION
Reads ModuleVersion and the prerelease label (PrivateData.PSData.Prerelease) from the module manifest and returns
the version to release, such as 5.0.0 or 5.0.0-rc1, with its release notes:
- A release without a prerelease label takes the notes of the section "## [<version>] - <yyyy-MM-dd>". The
section must exist and have a release date.
- A prerelease takes the notes of the section "## [Unreleased]", which must not be empty. CHANGELOG.md must not
have a section for the version yet; it gets one with the final release.
The notes are the text below the heading up to the next section or up to the link definitions at the end of the
file, with line feeds as line breaks.
.PARAMETER ManifestPath
Specifies the module manifest.
.PARAMETER ChangelogPath
Specifies CHANGELOG.md.
.EXAMPLE
.\.github\scripts\Get-ReleaseInfo.ps1 -ManifestPath .\NTFSSecurity\NTFSSecurity.psd1 -ChangelogPath .\CHANGELOG.md
Returns the version, whether it is a prerelease, the release date, and the release notes.
#>
[CmdletBinding()]
[OutputType([pscustomobject])]
param (
[Parameter(Mandatory)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })]
[string]
$ManifestPath,
[Parameter(Mandatory)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })]
[string]
$ChangelogPath
)
$ErrorActionPreference = 'Stop'
function Get-ChangelogSection {
<#
Returns the date and the notes of the section with the given name, such as 5.0.0 or Unreleased, or nothing if
CHANGELOG.md has no such section.
#>
param (
[Parameter(Mandatory)]
[string]
$Changelog,
[Parameter(Mandatory)]
[string]
$Name
)
$pattern = '(?ms)^## \[{0}\](?:[ \t]+-[ \t]+(?<Date>\S+))?[ \t]*$(?<Notes>.*?)(?=^## |^\[[^\]]+\]:[ \t]|\z)' -f
[regex]::Escape($Name)
$match = [regex]::Match($Changelog, $pattern)
if ($match.Success) {
[pscustomobject]@{
Date = $match.Groups['Date'].Value
Notes = $match.Groups['Notes'].Value.Trim()
}
}
}
$manifest = Import-PowerShellDataFile -LiteralPath $ManifestPath
$moduleVersion = "$($manifest.ModuleVersion)"
if ($moduleVersion -notmatch '^\d+\.\d+\.\d+$') {
throw "The module version '$moduleVersion' must have three parts, such as 5.0.0."
}
$prerelease = "$($manifest.PrivateData.PSData.Prerelease)"
if ($prerelease -and $prerelease -notmatch '^[A-Za-z][0-9A-Za-z-]*$') {
throw ("The prerelease label '$prerelease' isn't valid in the PowerShell Gallery. Use letters, digits, and " +
'hyphens only, starting with a letter, such as rc1.')
}
$changelog = (Get-Content -LiteralPath $ChangelogPath -Raw -Encoding UTF8) -replace '\r\n', "`n"
$versionSection = Get-ChangelogSection -Changelog $changelog -Name $moduleVersion
if ($prerelease) {
if ($versionSection) {
throw ("CHANGELOG.md already has a section for $moduleVersion, so $moduleVersion is released. A prerelease " +
'needs a higher module version.')
}
$section = Get-ChangelogSection -Changelog $changelog -Name 'Unreleased'
if (-not $section -or -not $section.Notes) {
throw "The [Unreleased] section of CHANGELOG.md is empty. It holds the notes of $moduleVersion-$prerelease."
}
$date = $null
} else {
if (-not $versionSection) {
throw ("CHANGELOG.md has no section for $moduleVersion. Rename the [Unreleased] section to " +
"[$moduleVersion] and add the release date.")
}
if ($versionSection.Date -notmatch '^\d{4}-\d{2}-\d{2}$') {
throw "The section of $moduleVersion in CHANGELOG.md has no release date in the format yyyy-MM-dd."
}
if (-not $versionSection.Notes) {
throw "The section of $moduleVersion in CHANGELOG.md is empty."
}
$section = $versionSection
$date = [datetime]::ParseExact($versionSection.Date, 'yyyy-MM-dd', [cultureinfo]::InvariantCulture)
}
[pscustomobject]@{
Version = if ($prerelease) { "$moduleVersion-$prerelease" } else { $moduleVersion }
ModuleVersion = $moduleVersion
Prerelease = $prerelease
IsPrerelease = [bool] $prerelease
Date = $date
Notes = $section.Notes
}

148
.github/scripts/New-ModulePackage.ps1

@ -0,0 +1,148 @@
<#
.SYNOPSIS
Builds the packages of a release from the build output of the module.
.DESCRIPTION
Copies the files that the FileList of the module manifest names from BuildPath into the folder NTFSSecurity in
DestinationPath, so that the packages contain no debug symbols or other build output, and checks the copy with
Test-ModuleManifest. Then it creates the NuGet package for the PowerShell Gallery with Compress-PSResource, named
after the version and the prerelease label, and adds the command tags that the PowerShell Gallery uses to list the
cmdlets of the module. Last, it creates NTFSSecurity.zip, which contains the folder NTFSSecurity, for the GitHub
release.
The script needs Compress-PSResource from Microsoft.PowerShell.PSResourceGet, which comes with PowerShell 7.4 and
later.
.PARAMETER BuildPath
Specifies the build output folder, such as NTFSSecurity\bin\Release.
.PARAMETER DestinationPath
Specifies the folder for the module folder and the packages. The script replaces the module folder and the
packages that an earlier run created there.
.EXAMPLE
.\.github\scripts\New-ModulePackage.ps1 -BuildPath .\NTFSSecurity\bin\Release -DestinationPath .\out
Creates the folder .\out\NTFSSecurity and the files .\out\NTFSSecurity.<version>.nupkg and .\out\NTFSSecurity.zip.
#>
[CmdletBinding()]
[OutputType([pscustomobject])]
param (
[Parameter(Mandatory)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]
$BuildPath,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$DestinationPath
)
$ErrorActionPreference = 'Stop'
$moduleName = 'NTFSSecurity'
if (-not (Get-Command -Name Compress-PSResource -ErrorAction SilentlyContinue)) {
throw 'Compress-PSResource was not found. Run the script in PowerShell 7.4 or later, which includes PSResourceGet.'
}
$buildRoot = (Resolve-Path -LiteralPath $BuildPath).ProviderPath
$manifestPath = Join-Path -Path $buildRoot -ChildPath "$moduleName.psd1"
if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
throw "The build output in '$buildRoot' has no module manifest $moduleName.psd1."
}
$manifest = Import-PowerShellDataFile -LiteralPath $manifestPath
$version = "$($manifest.ModuleVersion)"
if ($manifest.PrivateData.PSData.Prerelease) {
$version = '{0}-{1}' -f $version, $manifest.PrivateData.PSData.Prerelease
}
$missingFiles = @($manifest.FileList | Where-Object -FilterScript {
-not (Test-Path -LiteralPath (Join-Path -Path $buildRoot -ChildPath $_) -PathType Leaf)
})
if ($missingFiles) {
throw "The build output in '$buildRoot' lacks these files of the FileList: $($missingFiles -join ', ')."
}
$destinationRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath)
$modulePath = Join-Path -Path $destinationRoot -ChildPath $moduleName
$packagePath = Join-Path -Path $destinationRoot -ChildPath "$moduleName.$version.nupkg"
$zipPath = Join-Path -Path $destinationRoot -ChildPath "$moduleName.zip"
foreach ($path in $modulePath, $packagePath, $zipPath) {
if (Test-Path -LiteralPath $path) {
Remove-Item -LiteralPath $path -Recurse -Force
}
}
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null
foreach ($file in $manifest.FileList) {
$target = Join-Path -Path $modulePath -ChildPath $file
$targetFolder = Split-Path -Path $target -Parent
if (-not (Test-Path -LiteralPath $targetFolder)) {
New-Item -ItemType Directory -Path $targetFolder | Out-Null
}
Copy-Item -LiteralPath (Join-Path -Path $buildRoot -ChildPath $file) -Destination $target
}
$testParameters = @{
Path = Join-Path -Path $modulePath -ChildPath "$moduleName.psd1"
ErrorAction = 'Stop'
WarningVariable = 'manifestWarnings'
WarningAction = 'SilentlyContinue'
}
$null = Test-ModuleManifest @testParameters
if ($manifestWarnings) {
throw "Test-ModuleManifest reported warnings for the module in '$modulePath': $($manifestWarnings -join ' ')"
}
Compress-PSResource -Path $modulePath -DestinationPath $destinationRoot
if (-not (Test-Path -LiteralPath $packagePath -PathType Leaf)) {
throw "Compress-PSResource didn't create the package '$packagePath'."
}
<#
PSResourceGet doesn't add the command tags that PowerShellGet 2 added when it published 4.2.6. The PowerShell
Gallery lists the cmdlets of a module from these tags, and Find-Command searches them, so add the missing ones to
the nuspec in the package.
#>
$commandTags = @('PSIncludes_Cmdlet') + @($manifest.CmdletsToExport | Sort-Object -Unique |
ForEach-Object -Process { "PSCmdlet_$_"; "PSCommand_$_" })
$archive = [IO.Compression.ZipFile]::Open($packagePath, [IO.Compression.ZipArchiveMode]::Update)
try {
$stream = $archive.GetEntry("$moduleName.nuspec").Open()
try {
$nuspec = New-Object -TypeName 'System.Xml.XmlDocument'
$nuspec.Load($stream)
$tagsNode = $nuspec.SelectSingleNode("/*[local-name()='package']/*[local-name()='metadata']/*[local-name()='tags']")
if (-not $tagsNode) {
throw "The nuspec in the package '$packagePath' has no tags element."
}
$tags = @($tagsNode.InnerText -split '\s+' | Where-Object -FilterScript { $_ })
$tagsNode.InnerText = ($tags + @($commandTags | Where-Object -FilterScript { $_ -notin $tags })) -join ' '
$stream.SetLength(0)
$settings = New-Object -TypeName 'System.Xml.XmlWriterSettings'
$settings.Encoding = New-Object -TypeName 'System.Text.UTF8Encoding' -ArgumentList $false
$settings.Indent = $true
$writer = [Xml.XmlWriter]::Create($stream, $settings)
try {
$nuspec.Save($writer)
} finally {
$writer.Dispose()
}
} finally {
$stream.Dispose()
}
} finally {
$archive.Dispose()
}
[IO.Compression.ZipFile]::CreateFromDirectory($modulePath, $zipPath, [IO.Compression.CompressionLevel]::Optimal, $true)
[pscustomobject]@{
Version = $version
ModulePath = $modulePath
PackagePath = $packagePath
ZipPath = $zipPath
}

124
.github/workflows/ci.yml

@ -1,7 +1,10 @@
# Builds the NTFSSecurity module from source, checks that the cmdlet
# documentation in Docs/Cmdlets and the help file generated from it match the
# cmdlets of that build, runs the Pester tests in Windows PowerShell 5.1 and
# PowerShell 7, and publishes Docs to the GitHub wiki from master.
# PowerShell 7, and builds the packages. From master, it publishes Docs to the
# GitHub wiki. For a version tag, such as 5.0.0 or 5.0.0-rc1, it publishes the
# package to the PowerShell Gallery and creates the GitHub release; see
# Docs/Contributing/05-Releasing.md.
name: CI
on:
@ -9,6 +12,9 @@ on:
push:
branches:
- master
tags:
- '[0-9]+.[0-9]+.[0-9]+'
- '[0-9]+.[0-9]+.[0-9]+-*'
workflow_dispatch:
permissions:
@ -123,13 +129,28 @@ jobs:
path: TestResults/
if-no-files-found: ignore
# Only the files of the FileList, without debug symbols or other build output
- name: Build the packages
shell: pwsh
run: ./.github/scripts/New-ModulePackage.ps1 -BuildPath ./NTFSSecurity/bin/Release -DestinationPath ./out | Format-List
- name: Upload the packages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: packages
path: |
out/*.nupkg
out/NTFSSecurity.zip
if-no-files-found: error
wiki:
name: Wiki
needs: build
if: github.ref_type != 'tag'
runs-on: ubuntu-latest
timeout-minutes: 10
# Only this job can write: it publishes the wiki from master. On pull
# requests, it shows the pages that would change.
# This job can write: it publishes the wiki from master. On pull requests,
# it shows the pages that would change.
permissions:
contents: write
steps:
@ -179,3 +200,100 @@ jobs:
if ($LASTEXITCODE -ne 0) {
throw "Publishing the wiki failed with exit code $LASTEXITCODE."
}
release:
name: Release
needs: build
if: github.ref_type == 'tag'
runs-on: windows-2025
timeout-minutes: 15
# The API key of the PowerShell Gallery is a secret of this environment, so
# only this job can read it. The job can write to create the GitHub release.
environment: powershell-gallery
permissions:
contents: write
steps:
- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Download the packages
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: packages
path: out
- name: Check the release
id: release
shell: pwsh
run: |
$release = ./.github/scripts/Get-ReleaseInfo.ps1 -ManifestPath ./NTFSSecurity/NTFSSecurity.psd1 -ChangelogPath ./CHANGELOG.md
if ($env:GITHUB_REF_NAME -cne $release.Version) {
throw "The tag $env:GITHUB_REF_NAME doesn't match the version $($release.Version) in the module manifest."
}
git merge-base --is-ancestor $env:GITHUB_SHA origin/master
if ($LASTEXITCODE -ne 0) {
throw "The tag $env:GITHUB_REF_NAME doesn't point to a commit on master."
}
$package = "out/NTFSSecurity.$($release.Version).nupkg"
if (-not (Test-Path -LiteralPath $package) -or -not (Test-Path -LiteralPath 'out/NTFSSecurity.zip')) {
throw "The packages of $($release.Version) are missing."
}
$today = (Get-Date).ToUniversalTime().Date
if (-not $release.IsPrerelease -and $release.Date -ne $today) {
"::warning::CHANGELOG.md dates $($release.Version) $($release.Date.ToString('yyyy-MM-dd')), but it is released on $($today.ToString('yyyy-MM-dd'))."
}
$notes = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'release-notes.md'
Set-Content -LiteralPath $notes -Value $release.Notes -Encoding utf8NoBOM
Add-Content -LiteralPath $env:GITHUB_OUTPUT -Value @(
"version=$($release.Version)"
"prerelease=$($release.IsPrerelease.ToString().ToLowerInvariant())"
"package=$package"
"notes=$notes"
)
# Publishes the package that the build job built and tested. A rerun skips
# a version that the PowerShell Gallery already has.
- name: Publish to the PowerShell Gallery
shell: pwsh
env:
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
RELEASE_VERSION: ${{ steps.release.outputs.version }}
RELEASE_PACKAGE: ${{ steps.release.outputs.package }}
run: |
if (-not $env:PSGALLERY_API_KEY) {
throw 'The secret PSGALLERY_API_KEY of the environment powershell-gallery is not set.'
}
$published = Find-PSResource -Name NTFSSecurity -Version $env:RELEASE_VERSION -Prerelease -Repository PSGallery -ErrorAction SilentlyContinue
if ($published) {
"NTFSSecurity $env:RELEASE_VERSION is already in the PowerShell Gallery."
exit 0
}
Publish-PSResource -NupkgPath $env:RELEASE_PACKAGE -Repository PSGallery -ApiKey $env:PSGALLERY_API_KEY
- name: Create the GitHub release
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
RELEASE_VERSION: ${{ steps.release.outputs.version }}
RELEASE_PRERELEASE: ${{ steps.release.outputs.prerelease }}
RELEASE_NOTES: ${{ steps.release.outputs.notes }}
run: |
gh release view $env:RELEASE_VERSION --repo $env:GITHUB_REPOSITORY *> $null
if ($LASTEXITCODE -eq 0) {
"The GitHub release $env:RELEASE_VERSION exists."
exit 0
}
$arguments = @(
'release', 'create', $env:RELEASE_VERSION, 'out/NTFSSecurity.zip', '--repo', $env:GITHUB_REPOSITORY,
'--title', $env:RELEASE_VERSION, '--notes-file', $env:RELEASE_NOTES, '--verify-tag'
)
if ($env:RELEASE_PRERELEASE -eq 'true') {
$arguments += '--prerelease'
}
gh @arguments
if ($LASTEXITCODE -ne 0) {
throw "Creating the GitHub release failed with exit code $LASTEXITCODE."
}

44
.memory-bank/activeContext.md

@ -9,30 +9,36 @@ source: current task evidence
## Current focus
PRs #94, #95, and #96 are merged; CI and the wiki run on GitHub Actions.
The version history completed from the PowerShell Gallery packages is
PR-ready on the local branch `ai/version-history`; the maintainer pushes it
and opens the PR. Work package 5 (code defects) waits for the maintainer's
go-ahead.
Release 5.0.0 through CI, with the prerelease `5.0.0-rc1` first
(Decision 12). The release workflow, scripts, tests, and docs are PR-ready
on the local branch `ai/release-5.0.0`; the maintainer pushes it, opens the
PR, sets up the Gallery key and the environment `powershell-gallery`, and
tags `5.0.0-rc1` after the merge. Work package 5 (code defects) and the
open issues come after the release.
## Evidence
- Six Gallery packages compared (4.0.0, 4.2.2 to 4.2.6), each imported in
its own Windows PowerShell process: exported cmdlets 30, 35, 36, 36, 36,
36. `Show-SimpleAccess` was exported only by 4.0.0 (the manifest of 4.2.2
to 4.2.4 lists it as `Show-NTFSSimpleAccess`) and deleted in 4.2.5. All
versions export the aliases `dir2`, `gi2`, `rm2`, and `del2` only.
- 4.2.4 already carried the MIT license (`LicenseUri`), the setting
`IdentifyHardLinks`, and AlphaFS 2.2.1; 4.2.5 fixed the `-Account`
aliases (#18, #36) and #48 but broke the `Applies to` column, which 4.2.6
fixed (#57). 4.2.5 and 4.2.6 still ship AlphaFS 2.2.1: `d8f67af` updated
only `packages.config`, not the `HintPath`.
- `Wiki.Tests.ps1` passes with the changed page; markdownlint (with `MD024`
siblings only for `CHANGELOG.md`) and the link check found nothing.
- Test first: `Tests\Release.Tests.ps1` failed 15 of 15 (Windows
PowerShell: 9 failed, 6 skipped) before the scripts existed; the command
tag test failed before the tags were added. Final: full suite 268 tests,
PowerShell 7 232 passed and 36 skipped, Windows PowerShell 261 passed and
7 skipped (packaging needs PowerShell 7).
- Package dry run: `NTFSSecurity.5.0.0-rc1.nupkg` (about 275 KB) with the 11
`FileList` files, version `5.0.0-rc1`, release notes link, and 83 tags
(36 `PSCmdlet_`, 36 `PSCommand_`, `PSIncludes_Cmdlet`); the extracted
package imports in Windows PowerShell 5.1 and PowerShell 7.6.1 with 36
cmdlets and working help. 4.2.6 on the Gallery has 37 + 37 command tags;
PSResourceGet 1.2.0 `Compress-PSResource` adds none.
- actionlint, PSScriptAnalyzer, and markdownlint: no findings.
- `master` has 37 open issues; several overlap the work package 5 defects
(for example #4) or are already fixed (#19 in 4.2.4; #15, #47, #66 by the
documentation).
- The local branch `ai/read-the-docs` keeps the dropped strict-build work
(`886c874`, `325ec76`); delete it once it is no longer wanted.
## Next step
After the maintainer opens the PR: read its GitHub Actions run with
`gh pr checks`. Then wait for the go-ahead for work package 5.
After the maintainer opens the PR: read its CI run, and download the
`packages` artifact (`gh run download`) to compare it with the local dry
run. After the `5.0.0-rc1` tag: check the release job, the Gallery entry
(version, tags, `Find-Command Get-NTFSAccess`), and the GitHub prerelease.

19
.memory-bank/decisions/0010-one-version.md

@ -8,17 +8,22 @@ source: maintainer decisions in work package 4
# Decision 10: One version for the manifest, assemblies, and changelog
- Choice: `ModuleVersion` in `NTFSSecurity.psd1`, `AssemblyVersion` and
- Choice: `ModuleVersion` in `NTFSSecurity.psd1` and `AssemblyVersion` and
`AssemblyFileVersion` of `NTFSSecurity`, `Security2`, and
`PrivilegeControl` (as `x.y.z.0`), and the latest version section of
`CHANGELOG.md` carry the same version. The vendored `ProcessPrivileges`
and the unshipped `Log` keep their own versions.
`Tests\Manifest.Tests.ps1` enforces this.
`PrivilegeControl` (as `x.y.z.0`) carry the same version
(`Tests\Manifest.Tests.ps1`). The vendored `ProcessPrivileges` and the
unshipped `Log` keep their own versions. `CHANGELOG.md` describes that
version (`Tests\Release.Tests.ps1` through `Get-ReleaseInfo.ps1`): a
release has a dated section `## [x.y.z] - yyyy-MM-dd`; a prerelease has
its label in `PrivateData.PSData.Prerelease` and its notes under
`## [Unreleased]`, with no section for `x.y.z` yet (changelog Option A,
amended 2026-10-04 for the 5.0.0 prerelease).
- Rationale: Before, the manifest said 4.2.5, the release 4.2.6, and the
assemblies 4.2.1.0, 3.2.3.0, and 1.0.0.0; releases bumped the version
only in the published copy. One version, set in the repository before
the release, identifies a build.
- Consequence: A version bump changes all five places in one commit. The
date of the version section is the release date; update it when you tag.
- Consequence: A version bump changes the manifest and three assemblies in
one commit. The final release renames `[Unreleased]` to the dated version
section and removes the prerelease label.
- Context: 5.0.0 is major because the minimum PowerShell version rose to
5.1. `Remove-Item2 -PassThur` stays as a deprecated alias of `-PassThru`.

39
.memory-bank/decisions/0012-ci-releases.md

@ -0,0 +1,39 @@
---
status: accepted
date: 2026-10-04
last-verified: 2026-10-04
owner: shared
source: maintainer decisions after #97 (release first, prerelease first)
---
# Decision 12: Releases are built and published by CI on a version tag
- Choice: Pushing a tag such as `5.0.0` or `5.0.0-rc1` on `master` runs the
`release` job of `.github/workflows/ci.yml`. It publishes the package that
the `build` job built and tested to the PowerShell Gallery
(`Publish-PSResource -NupkgPath`) and creates the GitHub release with
`NTFSSecurity.zip`, marked as a prerelease for a prerelease tag. The job
checks that the tag equals the manifest version (with the prerelease
label) and points to a commit on `master`, and it skips a version the
Gallery or GitHub already has, so a rerun is safe.
- Package: `New-ModulePackage.ps1` copies only the `FileList` files of the
Release build, so no `.pdb`, XML documentation, or
`System.Management.Automation.dll` ships. `Compress-PSResource` builds the
nupkg; the script adds the command tags (`PSIncludes_Cmdlet`,
`PSCmdlet_<name>`, `PSCommand_<name>`) that PowerShellGet 2 added and
PSResourceGet 1.2 doesn't, because the Gallery lists cmdlets and
`Find-Command` searches by them. Every CI run builds the packages, so pull
requests test them.
- Secret: `PSGALLERY_API_KEY` belongs to the GitHub environment
`powershell-gallery`, which only the `release` job uses; the maintainer
creates the key, the environment, and the secret, and may require a
reviewer.
- Process: a prerelease first (maintainer, 2026-10-04: "no full release
without proper testing"), starting with `5.0.0-rc1`; the final release
removes the label and dates the changelog section. Steps for maintainers
are in `Docs/Contributing/05-Releasing.md`.
- Rationale: Releases so far were local Debug builds published by hand
with the whole output folder; tags carried the previous version.
- Rejected: publishing with PowerShellGet 2 `Publish-Module` (repackages at
publish time, so the tested package isn't the published one), and adding
the command tags to the shipped manifest.

41
.memory-bank/progress.md

@ -9,12 +9,12 @@ source: repository evidence
## Current status
PRs #91 to #96 are merged; `master` (`4f9f7cc`) carries version 5.0.0,
PRs #91 to #97 are merged; `master` (`59663c9`) carries version 5.0.0,
which is not released yet. CI runs on GitHub Actions: build, docs checks,
and tests in Windows PowerShell 5.1 and PowerShell 7, plus the wiki, which
is generated from `Docs` (43 pages). AppVeyor no longer reports on `master`.
PR-ready locally: `ai/version-history`, the version history completed from
the PowerShell Gallery packages.
is generated from `Docs` (43 pages). PR-ready locally: `ai/release-5.0.0`,
releases by CI on a version tag (Decision 12), starting with the
prerelease `5.0.0-rc1`.
## Recent milestones
@ -43,9 +43,15 @@ the PowerShell Gallery packages.
the wiki (`62ec94a`, 43 pages).
- 2026-10-04: The maintainer kept the version history separate from
`CHANGELOG.md` and had it completed from the six PowerShell Gallery
packages and the commit history: release dates, notes for 4.2.2, detailed
notes for 4.2.4, and separate notes for 4.2.5 and 4.2.6
(`ai/version-history`).
packages and the commit history (#97, `59663c9`): release dates, notes
for 4.2.2, detailed notes for 4.2.4, and separate notes for 4.2.5 and
4.2.6. The wiki republished it.
- 2026-10-04: The maintainer chose to release 5.0.0 next, through CI and a
prerelease first (Decision 12): `ai/release-5.0.0` adds the `release` job,
`Get-ReleaseInfo.ps1`, `New-ModulePackage.ps1`, `Tests\Release.Tests.ps1`,
the label `rc1`, and `Docs/Contributing/05-Releasing.md`. The package
dry run found that PSResourceGet drops the command tags that 4.2.6 had;
the script adds them back.
## Stable capabilities
@ -65,19 +71,22 @@ next package starts only after the maintainer's go-ahead.
1. Housekeeping: done (#92).
2. Ship help: done (#93).
3. Docs on GitHub: done (#94).
4. Manifest and version 5.0.0: done (#95). Before the release: set the date
of the 5.0.0 section to the release date (fold the `[Unreleased]` entries
into it), tag `5.0.0` (no `v` prefix), build in Release, and clean
`C:\Program Files\WindowsPowerShell\Modules\NTFSSecurity` before
`Publish-Module`; releases so far were Debug builds published with the
whole output folder (`.pdb`, `.xml`, `System.Management.Automation.dll`),
and the removed `NTFSSecurity-Help.xml` would ship again.
4. Manifest and version 5.0.0: done (#95).
4b. CI and the wiki on GitHub Actions: done (#96). Left to the maintainer:
revoke AppVeyor's GitHub access if it is still granted, consider
**Restrict editing to collaborators only** for the wiki, and optionally
ask `Sup3rlativ3` to delete the Read the Docs project.
4c. Version history from the PowerShell Gallery: PR-ready on
`ai/version-history`.
4c. Version history from the PowerShell Gallery: done (#97).
4d. Release 5.0.0 through CI (Decision 12): PR-ready on `ai/release-5.0.0`.
Before the first tag, the maintainer creates the Gallery API key, the
environment `powershell-gallery`, and its secret `PSGALLERY_API_KEY`
(steps in `Docs/Contributing/05-Releasing.md`). Then: merge, tag
`5.0.0-rc1`, test the prerelease, check its Gallery tags and
`Find-Command`, and for the final release remove the label and date the
changelog section. Releases no longer come from a local build, so the
old manual steps (cleaning
`C:\Program Files\WindowsPowerShell\Modules\NTFSSecurity`, Debug
builds) no longer apply.
5. Code defects, listed below: `review: on`, one PR per group, regression
test first. Pester 5 tests import `NTFSSecurity\bin\Release`, run in a
`$env:TEMP` sandbox and in the CI workflow (pattern:

11
.memory-bank/systemPatterns.md

@ -58,6 +58,7 @@ Each Decision record is a file in `decisions/`; read only the relevant ones.
| 9 | [Keep the documentation on GitHub](decisions/0009-docs-on-github.md) |
| 10 | [One version for the manifest, assemblies, and changelog](decisions/0010-one-version.md) |
| 11 | [CI and the wiki run on GitHub Actions](decisions/0011-github-actions.md) |
| 12 | [Releases are built and published by CI on a version tag](decisions/0012-ci-releases.md) |
## Patterns
@ -96,6 +97,10 @@ Each Decision record is a file in `decisions/`; read only the relevant ones.
failed tests go to the job summary, the NUnit file to the `test-results`
artifact, and it fails on failed test files too (`Result -ne 'Passed'`).
- `Tests\Manifest.Tests.ps1` checks the built manifest: `Test-ModuleManifest`
without errors or warnings, exactly 36 cmdlets, and one version
(Decision 10). Add a new cmdlet to `CmdletsToExport` and to the expected
count in the same change.
without errors or warnings, exactly 36 cmdlets, and the same version in
the manifest and the assemblies (Decision 10). Add a new cmdlet to
`CmdletsToExport` and to the expected count in the same change.
- `Tests\Release.Tests.ps1` checks that `CHANGELOG.md` has release notes
for the manifest version (dated section, or `[Unreleased]` for a
prerelease) and the packages: only `FileList` files, version with label,
command tags, and `NTFSSecurity.zip` with the module folder.

33
.memory-bank/techContext.md

@ -66,9 +66,11 @@ source: repository evidence
## Constraints
- `ModuleVersion` on `master` is `5.0.0` (not released); the latest tag and
Gallery release is `4.2.6`. The manifest requires PowerShell 5.1 and .NET
Framework 4.5.2, uses `RootModule`, and lists exactly 36 cmdlets;
`Test-ModuleManifest` passes in Windows PowerShell 5.1 and PowerShell 7.6.
Gallery release is `4.2.6`. On `ai/release-5.0.0`, the manifest adds the
prerelease label `rc1`, so the next tag is `5.0.0-rc1`. The manifest
requires PowerShell 5.1 and .NET Framework 4.5.2, uses `RootModule`, and
lists exactly 36 cmdlets; `Test-ModuleManifest` passes in Windows
PowerShell 5.1 and PowerShell 7.6.
- Besides the shipped help file and its tests (#93), the module source at
`master` differs from tag `4.2.6` by the `Remove-Item2 -PassThur` to
`-PassThru` rename (with a `-PassThur` alias), the manifest changes of
@ -78,12 +80,12 @@ source: repository evidence
4.2.6 (2019-07-12); none has release notes. Older versions were released
on CodePlex only, and their dates are lost. The git history starts on
2016-10-10, when the project moved from CodePlex.
- Releases have no script and no CI deployment. Evidence from 4.2.6: the
Gallery DLLs are Debug builds (`DebuggableAttribute` 263), the nuspec
comes from `Publish-Module`, the package holds the whole output folder
(`.pdb`, `AlphaFS.xml`, 7 MB `System.Management.Automation.dll`), and the
published manifest differs from the tag only by `ModuleVersion` (tags
carry the previous version). GitHub releases attach `NTFSSecurity.zip`.
- Releases up to 4.2.6 had no script and no CI deployment: the Gallery
DLLs are Debug builds (`DebuggableAttribute` 263), the nuspec comes from
`Publish-Module`, the package holds the whole output folder (`.pdb`,
`AlphaFS.xml`, 7 MB `System.Management.Automation.dll`), and tags carry
the previous version. From 5.0.0 on, CI publishes on a version tag
(Decision 12). GitHub releases attach `NTFSSecurity.zip`.
- CI: GitHub Actions on pull requests and pushes to `master` (Decision 11).
AppVeyor no longer reports on `master` (checked on `4f9f7cc`). The Read
the Docs project `ntfssecurity` (maintainer `Sup3rlativ3`) and a second
@ -118,8 +120,17 @@ source: repository evidence
Windows PowerShell 5.1 and in PowerShell 7. Job `wiki` on `ubuntu-latest`
clones the wiki (`gh auth setup-git` with the built-in token), runs
`Export-WikiContent.ps1`, lists the changed pages in the job summary, and
publishes from `master` only. Actions are pinned by commit SHA:
`actions/checkout` v7.0.1, `actions/upload-artifact` v7.0.1.
publishes from `master` only. After the tests, `build` runs
`New-ModulePackage.ps1` and uploads the artifact `packages` (nupkg and
`NTFSSecurity.zip`). Job `release` runs only for tags matching
`[0-9]+.[0-9]+.[0-9]+` or `[0-9]+.[0-9]+.[0-9]+-*`, in the environment
`powershell-gallery` (secret `PSGALLERY_API_KEY`); see Decision 12.
Actions are pinned by commit SHA: `actions/checkout` v7.0.1,
`actions/upload-artifact` v7.0.1, `actions/download-artifact` v8.0.1.
- Packaging needs PSResourceGet (`Compress-PSResource`, PowerShell 7.4 or
later); its tests skip in Windows PowerShell. Dry run locally: run
`New-ModulePackage.ps1` against `NTFSSecurity\bin\Release` into
`$env:TEMP`, then extract the nupkg into a folder and import it there.
- Read CI runs with `gh run list --repo raandree/NTFSSecurity --workflow
ci.yml`, `gh pr checks <number>`, and `gh run view <id> --log-failed`
(read-only).

19
CHANGELOG.md

@ -13,14 +13,7 @@ The format is based on
- Publish the documentation in the
[wiki](https://github.com/raandree/NTFSSecurity/wiki), generated from the
`Docs` folder after every change, with a sidebar that lists all cmdlets
### Changed
- Complete the version history with the release dates from the PowerShell
Gallery, notes for 4.2.2, detailed notes for 4.2.4, and separate notes for
4.2.5 and 4.2.6
## [5.0.0] - 2026-10-04
- Link the release notes in the PowerShell Gallery to this changelog
### Changed
@ -32,12 +25,17 @@ The format is based on
- Rename the `-PassThur` parameter of `Remove-Item2` to `-PassThru`;
`-PassThur` still works as an alias
([#64](https://github.com/raandree/NTFSSecurity/pull/64))
- Publish the module as a Release build that contains only the module
files; 4.2.6 was a Debug build with debug symbols and a copy of
`System.Management.Automation.dll`
- Document every cmdlet with synopsis, description, parameters, examples,
inputs, outputs, and notes, checked against the source code
- Rewrite the home, concepts, examples, and contributor pages to match the
current cmdlets, including module settings, privileges, and long paths
- Move the version history and the installation instructions from the wiki
into the documentation, and add the missing notes for 4.2.5 and 4.2.6
into the documentation, and complete the version history with the release
dates from the PowerShell Gallery, the missing notes for 4.2.2, 4.2.5, and
4.2.6, and detailed notes for 4.2.4
### Deprecated
@ -55,5 +53,4 @@ The format is based on
entries from the cmdlets that the module manifest exports and the
PowerShell Gallery lists
[Unreleased]: https://github.com/raandree/NTFSSecurity/compare/5.0.0...HEAD
[5.0.0]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...5.0.0
[Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD

3
Docs/Contributing.md

@ -10,5 +10,8 @@ documentation:
3. [Style guide](Contributing/03-Style-Guide.md)
4. [Markdown and platyPS specifics](Contributing/04-Markdown-Specifics.md)
Maintainers release new versions as described in
[Release a new version](Contributing/05-Releasing.md).
This guide is adapted from the contributor guide of the
[PowerShell documentation](https://github.com/MicrosoftDocs/PowerShell-Docs).

2
Docs/Contributing/02-Writing.md

@ -18,7 +18,7 @@ This page explains how the documentation is organized and how to change it.
| `Docs/Contributing.md`, `Docs/Contributing/*.md` | This contributor guide |
| `README.md` | Front page of the GitHub repository |
| `CHANGELOG.md` | Changes since 4.2.6 |
| `.github/workflows/ci.yml` | CI build: documentation checks, tests, and wiki publishing |
| `.github/workflows/ci.yml` | CI build: documentation checks, tests, packages, wiki publishing, and releases |
| `.github/scripts/Export-WikiContent.ps1` | Converts `Docs` into the pages of the wiki |
When you add a page, link to it from `Docs/README.md` or from a related page,

110
Docs/Contributing/05-Releasing.md

@ -0,0 +1,110 @@
# Release a new version
This page explains how a maintainer releases a new version of NTFSSecurity.
The CI workflow does the work: when you push a version tag, it builds and
tests the module, publishes the package to the PowerShell Gallery, and creates
the GitHub release with `NTFSSecurity.zip`.
## Prepare the repository once
1. In the PowerShell Gallery, [create an API key][api-key] for the account
that owns NTFSSecurity. Select the scope **Push new versions of existing
packages**, enter `NTFSSecurity` as the glob pattern, and choose an
expiration.
2. In the settings of the repository on GitHub, under **Environments**,
create the environment `powershell-gallery`. To approve every release
before it's published, add yourself as a required reviewer. Under
**Deployment branches and tags**, allow only tags such as
`[0-9]*.[0-9]*.[0-9]*`.
3. Add the API key as the secret `PSGALLERY_API_KEY` of the environment.
The command asks for the key:
```powershell
gh secret set PSGALLERY_API_KEY --env powershell-gallery --repo raandree/NTFSSecurity
```
Renew the API key before it expires, and update the secret.
## Versions
- `ModuleVersion` in `NTFSSecurity\NTFSSecurity.psd1` and the
`AssemblyVersion` and `AssemblyFileVersion` of the `NTFSSecurity`,
`Security2`, and `PrivilegeControl` projects carry the same version. The
tests in `Tests\Manifest.Tests.ps1` check this.
- A prerelease, such as `5.0.0-rc1`, has its label in `Prerelease` under
`PrivateData.PSData` of the module manifest. Its release notes are the
`[Unreleased]` section of `CHANGELOG.md`. The PowerShell Gallery installs a
prerelease only when you add `-AllowPrerelease`.
- A release, such as `5.0.0`, has no `Prerelease` value. Its release notes
are the section `## [5.0.0] - <date>` of `CHANGELOG.md`.
The tests in `Tests\Release.Tests.ps1` check that `CHANGELOG.md` has the
release notes for the version of the module manifest.
## Publish a prerelease
1. Set the version and the `Prerelease` label, such as `rc1`, and make sure
that the `[Unreleased]` section of `CHANGELOG.md` describes the changes.
Merge the change into `master`.
2. Tag the commit on `master` with the version and push the tag:
```powershell
git switch master
git pull
git tag 5.0.0-rc1
git push origin 5.0.0-rc1
```
3. Watch the CI run of the tag. The **Release** job checks that the tag
matches the version in the module manifest and points to a commit on
`master`. Then it publishes the package and creates a GitHub prerelease.
If the environment requires a reviewer, approve the deployment.
4. Install the prerelease in a test environment and test it:
```powershell
Install-Module -Name NTFSSecurity -AllowPrerelease -Scope CurrentUser
```
For another prerelease, increase the label, such as `rc2`. The PowerShell
Gallery compares labels as text, so `rc10` sorts before `rc2`.
## Publish a release
1. Remove the `Prerelease` value from the module manifest.
2. In `CHANGELOG.md`, rename `## [Unreleased]` to the version with the
release date, such as `## [5.0.0] - 2026-10-31`, add an empty
`## [Unreleased]` section above it, and update the links at the end of the
file:
```markdown
[Unreleased]: https://github.com/raandree/NTFSSecurity/compare/5.0.0...HEAD
[5.0.0]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...5.0.0
```
3. Merge the change into `master`. Then tag the commit with the version, such
as `5.0.0`, and push the tag, as for a prerelease. The **Release** job
warns if the date in `CHANGELOG.md` isn't the day of the release.
## If a release fails
The **Release** job skips what's already done: a version that the PowerShell
Gallery already has, and a GitHub release that already exists. If the
failure doesn't need a change in the repository, fix the cause and rerun the
failed job.
If the fix needs a change in the repository and the PowerShell Gallery
doesn't have the version yet, delete the tag, merge the fix, and tag the new
commit:
```powershell
git push origin --delete 5.0.0-rc1
git tag --delete 5.0.0-rc1
```
The PowerShell Gallery never accepts the same version twice. To replace a
published version, publish the next one, such as `5.0.0-rc2`, and
[unlist][unlist] the faulty version.
<!-- External URLs -->
[api-key]: https://learn.microsoft.com/powershell/gallery/how-to/managing-profile/creating-apikeys
[unlist]: https://learn.microsoft.com/powershell/gallery/how-to/publishing-packages/unlisting-packages

3
Docs/README.md

@ -38,6 +38,9 @@ Install the module from the
Install-Module -Name NTFSSecurity
```
To try a prerelease of the next version, such as `5.0.0-rc1`, add
`-AllowPrerelease`. A prerelease is for testing; don't use it in production.
You can also install a release without the PowerShell Gallery. Download
`NTFSSecurity.zip` from the
[releases page](https://github.com/raandree/NTFSSecurity/releases) on GitHub

9
NTFSSecurity/NTFSSecurity.psd1

@ -97,9 +97,12 @@
IdentifyHardLinks = $true
PSData = @{
Tags = @('AccessControl', 'ACL', 'DirectorySecurity', 'FileSecurity', 'FileSystem', 'FileSystemSecurity', 'NTFS', 'Module', 'AccessRights')
LicenseUri = 'https://github.com/raandree/NTFSSecurity/blob/master/LICENSE'
ProjectUri = 'https://github.com/raandree/NTFSSecurity'
Tags = @('AccessControl', 'ACL', 'DirectorySecurity', 'FileSecurity', 'FileSystem', 'FileSystemSecurity', 'NTFS', 'Module', 'AccessRights')
LicenseUri = 'https://github.com/raandree/NTFSSecurity/blob/master/LICENSE'
ProjectUri = 'https://github.com/raandree/NTFSSecurity'
ReleaseNotes = 'https://github.com/raandree/NTFSSecurity/blob/master/CHANGELOG.md'
# Remove the prerelease label for the final release, see Docs/Contributing/05-Releasing.md
Prerelease = 'rc1'
}
}
}

10
Tests/Manifest.Tests.ps1

@ -1,6 +1,7 @@
<#
Tests the module manifest of the module built in NTFSSecurity\bin\Release: it passes Test-ModuleManifest,
exports exactly the cmdlets of the module, and carries the same version as the assemblies and CHANGELOG.md.
exports exactly the cmdlets of the module, and carries the same version as the assemblies. Release.Tests.ps1
checks that CHANGELOG.md describes that version.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
@ -62,12 +63,5 @@ Describe 'Module manifest of NTFSSecurity' {
$assemblyVersion.ToString(3) | Should -BeExactly $manifest.ModuleVersion
$fileVersion.ToString(3) | Should -BeExactly $manifest.ModuleVersion
}
It 'Should describe the module version in the latest section of CHANGELOG.md' {
$changelog = Get-Content -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath '..\CHANGELOG.md') -Raw
$latestVersion = [regex]::Match($changelog, '(?m)^## \[(?<Version>\d+\.\d+\.\d+)\]').Groups['Version'].Value
$latestVersion | Should -BeExactly $manifest.ModuleVersion
}
}
}

255
Tests/Release.Tests.ps1

@ -0,0 +1,255 @@
<#
Tests the release scripts in .github\scripts: Get-ReleaseInfo.ps1, which reads the version of the module and its
release notes, and New-ModulePackage.ps1, which builds the packages from the module built in
NTFSSecurity\bin\Release.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'The test helpers write only to TestDrive.'
)]
param ()
BeforeDiscovery {
# Compress-PSResource is part of Microsoft.PowerShell.PSResourceGet, which comes with PowerShell 7.4 and later.
$canPackage = [bool](Get-Command -Name Compress-PSResource -ErrorAction SilentlyContinue)
}
Describe 'Get-ReleaseInfo.ps1' {
BeforeAll {
$scriptPath = Join-Path -Path $PSScriptRoot -ChildPath '..\.github\scripts\Get-ReleaseInfo.ps1'
$manifestPath = Join-Path -Path $TestDrive -ChildPath 'Module.psd1'
function Set-TestManifest {
param (
[Parameter(Mandatory)]
[string]
$Version,
[Parameter()]
[string]
$Prerelease
)
$psData = if ($Prerelease) { "Prerelease = '$Prerelease'" } else { '' }
Set-Content -LiteralPath $manifestPath -Value "@{ ModuleVersion = '$Version'; PrivateData = @{ PSData = @{ $psData } } }"
}
$changelogPath = Join-Path -Path $TestDrive -ChildPath 'CHANGELOG.md'
Set-Content -LiteralPath $changelogPath -Value @'
# Changelog
## [Unreleased]
### Fixed
- Fix a thing
## [2.0.0] - 2026-01-02
### Changed
- Change a thing
## [1.0.0]
### Added
- Add a thing
[Unreleased]: https://example.com/compare/2.0.0...HEAD
[2.0.0]: https://example.com/compare/1.0.0...2.0.0
'@
$releasedChangelogPath = Join-Path -Path $TestDrive -ChildPath 'CHANGELOG-released.md'
Set-Content -LiteralPath $releasedChangelogPath -Value @'
# Changelog
## [Unreleased]
## [2.0.0] - 2026-01-02
### Changed
- Change a thing
[Unreleased]: https://example.com/compare/2.0.0...HEAD
[2.0.0]: https://example.com/compare/1.0.0...2.0.0
'@
}
Context 'When the module manifest has no prerelease label' {
It 'Should return the version, the release date, and the notes of its section' {
Set-TestManifest -Version '2.0.0'
$release = & $scriptPath -ManifestPath $manifestPath -ChangelogPath $changelogPath
$release.Version | Should -BeExactly '2.0.0'
$release.IsPrerelease | Should -BeFalse
$release.Date | Should -Be ([datetime] '2026-01-02')
$release.Notes | Should -BeExactly "### Changed`n`n- Change a thing"
}
It 'Should not include the link definitions after the last section in the notes' {
Set-TestManifest -Version '2.0.0'
$release = & $scriptPath -ManifestPath $manifestPath -ChangelogPath $releasedChangelogPath
$release.Notes | Should -BeExactly "### Changed`n`n- Change a thing"
}
It 'Should fail if the section of the version has no release date' {
Set-TestManifest -Version '1.0.0'
{ & $scriptPath -ManifestPath $manifestPath -ChangelogPath $changelogPath } | Should -Throw '*1.0.0*date*'
}
It 'Should fail if CHANGELOG.md has no section for the version' {
Set-TestManifest -Version '3.0.0'
{ & $scriptPath -ManifestPath $manifestPath -ChangelogPath $changelogPath } | Should -Throw '*3.0.0*'
}
}
Context 'When the module manifest has a prerelease label' {
It 'Should return the prerelease version and the notes of the Unreleased section' {
Set-TestManifest -Version '3.0.0' -Prerelease 'rc1'
$release = & $scriptPath -ManifestPath $manifestPath -ChangelogPath $changelogPath
$release.Version | Should -BeExactly '3.0.0-rc1'
$release.IsPrerelease | Should -BeTrue
$release.Date | Should -BeNullOrEmpty
$release.Notes | Should -BeExactly "### Fixed`n`n- Fix a thing"
}
It 'Should fail if the Unreleased section is empty' {
Set-TestManifest -Version '3.0.0' -Prerelease 'rc1'
{ & $scriptPath -ManifestPath $manifestPath -ChangelogPath $releasedChangelogPath } |
Should -Throw '*Unreleased*'
}
It 'Should fail if CHANGELOG.md already has a section for the version' {
Set-TestManifest -Version '2.0.0' -Prerelease 'rc1'
{ & $scriptPath -ManifestPath $manifestPath -ChangelogPath $changelogPath } | Should -Throw '*2.0.0*'
}
It 'Should fail if the label is not valid in the PowerShell Gallery' {
Set-TestManifest -Version '3.0.0' -Prerelease 'rc.1'
{ & $scriptPath -ManifestPath $manifestPath -ChangelogPath $changelogPath } | Should -Throw '*rc.1*'
}
}
Context 'When it reads the module manifest and CHANGELOG.md of the repository' {
It 'Should return release notes for the version of the module manifest' {
$repositoryPath = Join-Path -Path $PSScriptRoot -ChildPath '..'
$sourceManifestPath = Join-Path -Path $repositoryPath -ChildPath 'NTFSSecurity\NTFSSecurity.psd1'
$manifest = Import-PowerShellDataFile -LiteralPath $sourceManifestPath
$release = & $scriptPath -ManifestPath $sourceManifestPath -ChangelogPath (Join-Path -Path $repositoryPath -ChildPath 'CHANGELOG.md')
$release.Version | Should -BeLike "$($manifest.ModuleVersion)*"
$release.Notes | Should -Not -BeNullOrEmpty
$release.Notes | Should -Not -Match '(?m)^\[[^\]]+\]: '
}
}
}
Describe 'New-ModulePackage.ps1' -Skip:(-not $canPackage) {
BeforeAll {
$scriptPath = Join-Path -Path $PSScriptRoot -ChildPath '..\.github\scripts\New-ModulePackage.ps1'
$buildPath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release'
$manifest = Import-PowerShellDataFile -LiteralPath (Join-Path -Path $buildPath -ChildPath 'NTFSSecurity.psd1')
$version = $manifest.ModuleVersion
if ($manifest.PrivateData.PSData.Prerelease) {
$version = '{0}-{1}' -f $version, $manifest.PrivateData.PSData.Prerelease
}
$expectedFiles = @($manifest.FileList | ForEach-Object -Process { $_ -replace '\\', '/' } | Sort-Object)
function Get-ZipEntryName {
param (
[Parameter(Mandatory)]
[string]
$Path
)
$zip = [IO.Compression.ZipFile]::OpenRead($Path)
try {
$zip.Entries | Where-Object -Property Name | ForEach-Object -Process { $_.FullName }
} finally {
$zip.Dispose()
}
}
$package = & $scriptPath -BuildPath $buildPath -DestinationPath (Join-Path -Path $TestDrive -ChildPath 'out')
}
It 'Should copy exactly the files of the FileList into the module folder' {
$files = Get-ChildItem -LiteralPath $package.ModulePath -Recurse -File |
ForEach-Object -Process { $_.FullName.Substring($package.ModulePath.Length + 1) -replace '\\', '/' }
($files | Sort-Object) -join ', ' | Should -BeExactly ($expectedFiles -join ', ')
}
It 'Should name the NuGet package after the version, including the prerelease label' {
Split-Path -Path $package.PackagePath -Leaf | Should -BeExactly "NTFSSecurity.$version.nupkg"
$package.Version | Should -BeExactly $version
}
It 'Should put exactly the module files into the NuGet package' {
$entries = Get-ZipEntryName -Path $package.PackagePath |
Where-Object -FilterScript { $_ -notmatch '^(_rels/|package/|\[Content_Types\]\.xml$|NTFSSecurity\.nuspec$)' }
($entries | Sort-Object) -join ', ' | Should -BeExactly ($expectedFiles -join ', ')
}
It 'Should set the version and the link to the release notes in the NuGet package' {
$zip = [IO.Compression.ZipFile]::OpenRead($package.PackagePath)
try {
$reader = New-Object -TypeName 'System.IO.StreamReader' -ArgumentList $zip.GetEntry('NTFSSecurity.nuspec').Open()
$nuspec = [xml] $reader.ReadToEnd()
$reader.Dispose()
} finally {
$zip.Dispose()
}
$nuspec.package.metadata.version | Should -BeExactly $version
$nuspec.package.metadata.releaseNotes | Should -Match 'https://github\.com/raandree/NTFSSecurity/blob/master/CHANGELOG\.md'
}
It 'Should tag the NuGet package with its cmdlets, which the PowerShell Gallery lists and Find-Command searches' {
$zip = [IO.Compression.ZipFile]::OpenRead($package.PackagePath)
try {
$reader = New-Object -TypeName 'System.IO.StreamReader' -ArgumentList $zip.GetEntry('NTFSSecurity.nuspec').Open()
$tags = ([xml] $reader.ReadToEnd()).package.metadata.tags -split '\s+'
$reader.Dispose()
} finally {
$zip.Dispose()
}
$expectedTags = @('PSModule', 'PSIncludes_Cmdlet') + $manifest.PrivateData.PSData.Tags +
@($manifest.CmdletsToExport | ForEach-Object -Process { "PSCmdlet_$_"; "PSCommand_$_" })
$expectedTags | Where-Object -FilterScript { $_ -notin $tags } | Should -BeNullOrEmpty
$tags | Group-Object | Where-Object -Property Count -GT -Value 1 | ForEach-Object -Process { $_.Name } |
Should -BeNullOrEmpty
}
It 'Should put the module folder into NTFSSecurity.zip' {
$entries = Get-ZipEntryName -Path $package.ZipPath | Sort-Object
$entries -join ', ' | Should -BeExactly (($expectedFiles | ForEach-Object -Process { "NTFSSecurity/$_" }) -join ', ')
}
It 'Should fail if a file of the FileList is missing from the build output' {
$incompletePath = Join-Path -Path $TestDrive -ChildPath 'incomplete'
Copy-Item -LiteralPath $buildPath -Destination $incompletePath -Recurse
Remove-Item -LiteralPath (Join-Path -Path $incompletePath -ChildPath 'en-US\NTFSSecurity.dll-Help.xml')
{ & $scriptPath -BuildPath $incompletePath -DestinationPath (Join-Path -Path $TestDrive -ChildPath 'out-incomplete') } |
Should -Throw '*NTFSSecurity.dll-Help.xml*'
}
}
Loading…
Cancel
Save