Browse Source

Merge pull request #96 from raandree/ai/github-actions

ci: move CI from AppVeyor to GitHub Actions and publish the docs to the wiki
pull/97/head
Raimund Andrée 1 week ago
committed by GitHub
parent
commit
4f9f7ccf58
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 279
      .github/scripts/Export-WikiContent.ps1
  2. 79
      .github/scripts/Invoke-Tests.ps1
  3. 181
      .github/workflows/ci.yml
  4. 46
      .memory-bank/activeContext.md
  5. 5
      .memory-bank/decisions/0006-ci-checks-docs-against-build.md
  6. 7
      .memory-bank/decisions/0008-commit-generated-help.md
  7. 13
      .memory-bank/decisions/0009-docs-on-github.md
  8. 27
      .memory-bank/decisions/0011-github-actions.md
  9. 43
      .memory-bank/progress.md
  10. 2
      .memory-bank/projectbrief.md
  11. 28
      .memory-bank/systemPatterns.md
  12. 71
      .memory-bank/techContext.md
  13. 6
      CHANGELOG.md
  14. 48
      Docs/Contributing/02-Writing.md
  15. 4
      Docs/Version-History.md
  16. 4
      README.md
  17. 228
      Tests/Wiki.Tests.ps1
  18. 86
      appveyor.yml

279
.github/scripts/Export-WikiContent.ps1

@ -0,0 +1,279 @@
<#
.SYNOPSIS
Converts the documentation in the Docs folder into the pages of the GitHub wiki.
.DESCRIPTION
Writes a wiki page for every page in Docs except the contributor guide (Contributing.md and the Contributing
folder). Docs/README.md becomes the page Home, and every other page keeps its file name, so a page in
Docs/Cmdlets becomes a page named after its cmdlet. The platyPS metadata at the top of the cmdlet pages is
removed. Relative links point to the wiki pages; links to other files of the repository point to the files on
GitHub. Links in code stay unchanged.
The script also writes the sidebar, which lists the cmdlets in the groups of the cmdlet list in Docs/README.md;
the footer; and the page How-to-install, which keeps the address of the former wiki page working.
The script removes everything in DestinationPath except the .git folder, so that pages that no longer exist in
Docs disappear from the wiki.
.PARAMETER Path
Specifies the Docs folder of the repository.
.PARAMETER DestinationPath
Specifies the folder to write the wiki pages to, usually a clone of the wiki repository. The script creates the
folder if it doesn't exist.
.PARAMETER RepositoryUrl
Specifies the address of the repository on GitHub, for links to files that aren't wiki pages.
.PARAMETER Branch
Specifies the branch for links to files that aren't wiki pages.
.EXAMPLE
git clone https://github.com/raandree/NTFSSecurity.wiki.git $env:TEMP\wiki
.\.github\scripts\Export-WikiContent.ps1 -Path .\Docs -DestinationPath $env:TEMP\wiki
git -C $env:TEMP\wiki status
Writes the wiki pages into a clone of the wiki and shows which pages change.
#>
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory)]
[ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })]
[string]
$Path,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$DestinationPath,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]
$RepositoryUrl = 'https://github.com/raandree/NTFSSecurity',
[Parameter()]
[ValidateNotNullOrEmpty()]
[string]
$Branch = 'master'
)
$ErrorActionPreference = 'Stop'
function Resolve-RepositoryPath {
<#
Returns the path of a link target relative to the repository root, with / as separator, or nothing if the
link leaves the repository.
#>
param (
[Parameter(Mandatory)]
[string]
$Directory,
[Parameter(Mandatory)]
[string]
$Link
)
$segments = New-Object -TypeName 'System.Collections.Generic.List[string]'
foreach ($segment in (('{0}/{1}' -f $Directory, $Link) -split '/')) {
if ($segment -eq '..') {
if ($segments.Count -eq 0) {
return
}
$segments.RemoveAt($segments.Count - 1)
} elseif ($segment -and $segment -ne '.') {
$segments.Add($segment)
}
}
$segments -join '/'
}
function ConvertTo-WikiLink {
<#
Returns the wiki address of a link on a page in Directory: the name of a wiki page, the address of a file of
the repository on GitHub, or the link itself if it is absolute or points to the same page.
#>
param (
[Parameter(Mandatory)]
[string]
$Url,
[Parameter(Mandatory)]
[string]
$Directory
)
if ($Url -match '^(?:[a-zA-Z][a-zA-Z0-9+.-]*:|//|#)') {
return $Url
}
$linkPath, $anchor = $Url -split '#', 2
$target = Resolve-RepositoryPath -Directory $Directory -Link ([uri]::UnescapeDataString($linkPath))
if (-not $target) {
return $Url
}
$fragment = if ($anchor) { '#' + $anchor } else { '' }
if ($pageNames.ContainsKey($target)) {
return $pageNames[$target] + $fragment
}
$view = if (Test-Path -LiteralPath (Join-Path -Path $repositoryRoot -ChildPath $target) -PathType Container) {
'tree'
} else {
'blob'
}
'{0}/{1}/{2}/{3}{4}' -f $RepositoryUrl.TrimEnd('/'), $view, $Branch, $target, $fragment
}
function Convert-MarkdownLink {
<#
Returns the Markdown text with the links of a page in Directory converted to wiki addresses. Links in code
spans and fenced code blocks stay unchanged.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSReviewUnusedParameter', 'Directory', Justification = 'The match evaluator script block uses it.'
)]
param (
[Parameter(Mandatory)]
[AllowEmptyString()]
[string]
$Markdown,
[Parameter(Mandatory)]
[string]
$Directory
)
$inlinePattern = '(?<code>(?<ticks>`+).+?\k<ticks>)|(?<prefix>!?\[(?:[^\[\]`]|`[^`]*`)*\]\()(?<url>[^)\s]+)(?<suffix>(?:\s+"[^"]*")?\))'
$definitionPattern = '^(?<prefix>\s{0,3}\[[^\]]+\]:\s*)(?<url>\S+)(?<suffix>.*)$'
$convertLink = {
param ($match)
if ($match.Groups['code'].Success) {
return $match.Value
}
$match.Groups['prefix'].Value + (ConvertTo-WikiLink -Url $match.Groups['url'].Value -Directory $Directory) +
$match.Groups['suffix'].Value
}
$fence = $null
$lines = foreach ($line in ($Markdown -split '\r?\n')) {
if ($line -match '^\s{0,3}(?<fence>`{3,}|~{3,})') {
if (-not $fence) {
$fence = $Matches['fence']
} elseif ($Matches['fence'].StartsWith($fence)) {
$fence = $null
}
$line
continue
}
if ($fence) {
$line
continue
}
$line = [regex]::Replace($line, $inlinePattern, $convertLink)
[regex]::Replace($line, $definitionPattern, $convertLink)
}
$lines -join "`n"
}
$docsRoot = (Resolve-Path -LiteralPath $Path).ProviderPath.TrimEnd('\', '/')
$repositoryRoot = Split-Path -Path $docsRoot -Parent
$docsName = Split-Path -Path $docsRoot -Leaf
$destinationRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath).TrimEnd('\', '/')
# Refuse a destination that contains the documentation, such as the repository itself.
$separator = [IO.Path]::DirectorySeparatorChar
if (($docsRoot + $separator).StartsWith($destinationRoot + $separator, [StringComparison]::OrdinalIgnoreCase)) {
throw "The destination '$destinationRoot' contains the documentation in '$docsRoot'. Specify a clone of the wiki."
}
# Collect the pages: Docs/README.md is the home page, and the contributor guide stays in the repository.
$pageNames = @{}
$pages = foreach ($file in Get-ChildItem -LiteralPath $docsRoot -Filter '*.md' -File -Recurse) {
$repositoryPath = $file.FullName.Substring($repositoryRoot.Length + 1) -replace '\\', '/'
if ($repositoryPath -match ('^{0}/Contributing(?:\.md$|/)' -f [regex]::Escape($docsName))) {
continue
}
$name = if ($repositoryPath -eq "$docsName/README.md") { 'Home' } else { $file.BaseName }
if ($pageNames.Values -contains $name) {
throw "Two pages in '$docsRoot' would become the wiki page '$name'."
}
$pageNames[$repositoryPath] = $name
$content = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8
$title = if ($content -match '(?m)^#\s+(?<title>.+?)\s*$') { $Matches['title'] } else { $name -replace '-', ' ' }
[pscustomobject]@{
Name = $name
Title = $title
Directory = $repositoryPath.Substring(0, $repositoryPath.LastIndexOf('/'))
Content = $content
}
}
# Remove the old pages, but keep the history of the wiki.
if (Test-Path -LiteralPath $destinationRoot) {
foreach ($item in Get-ChildItem -LiteralPath $destinationRoot -Force | Where-Object -Property Name -NE -Value '.git') {
if ($PSCmdlet.ShouldProcess($item.FullName, 'Remove')) {
Remove-Item -LiteralPath $item.FullName -Recurse -Force
}
}
} elseif ($PSCmdlet.ShouldProcess($destinationRoot, 'Create folder')) {
New-Item -ItemType Directory -Path $destinationRoot | Out-Null
}
$wikiPages = [ordered]@{}
foreach ($page in $pages) {
$content = [regex]::Replace($page.Content, '\A---\r?\n.*?\r?\n---[ \t]*(?:\r?\n|\z)\s*', '', 'Singleline')
$wikiPages[$page.Name] = Convert-MarkdownLink -Markdown $content -Directory $page.Directory
}
# The sidebar links the other pages and the cmdlets, grouped like the cmdlet list of the home page.
$homePage = $pages | Where-Object -Property Name -EQ -Value 'Home'
$sidebar = New-Object -TypeName 'System.Collections.Generic.List[string]'
$sidebar.Add('### [Home](Home)')
$sidebar.Add('')
foreach ($page in $pages | Where-Object { $_.Directory -eq $docsName -and $_.Name -ne 'Home' } | Sort-Object -Property Name) {
$sidebar.Add(('- [{0}]({1})' -f $page.Title, $page.Name))
}
if ($homePage) {
$section = $null
foreach ($line in ($homePage.Content -split '\r?\n')) {
if ($line -match '^##\s+(?<title>.+?)\s*$') {
$section = $Matches['title']
if ($section -eq 'Cmdlets') {
$sidebar.Add('')
$sidebar.Add('### Cmdlets')
}
} elseif ($section -eq 'Cmdlets' -and $line -match '^###\s+(?<title>.+?)\s*$') {
$sidebar.Add('')
$sidebar.Add(('**{0}**' -f $Matches['title']))
$sidebar.Add('')
} elseif ($section -eq 'Cmdlets' -and $line -match '^\|\s*\[(?<name>[^\]]+)\]\((?<url>[^)\s]+)\)') {
$sidebar.Add(('- [{0}]({1})' -f $Matches['name'], (ConvertTo-WikiLink -Url $Matches['url'] -Directory $docsName)))
}
}
}
$wikiPages['_Sidebar'] = $sidebar -join "`n"
$wikiPages['_Footer'] = ('This wiki is generated from the [{0}]({1}/tree/{2}/{0}) folder of the repository. ' +
'To change a page, edit its file there; changes made in the wiki are overwritten.') -f $docsName, $RepositoryUrl.TrimEnd('/'), $Branch
# The former wiki page How-to-install is linked from outside; it now points to the installation steps.
if ($homePage -and $homePage.Content -match '(?m)^##\s+Installation\s*$') {
$wikiPages['How-to-install'] = "# How to install`n`nThe installation steps are in the [Installation](Home#installation) section of the [Home](Home) page."
}
$encoding = New-Object -TypeName 'System.Text.UTF8Encoding' -ArgumentList $false
foreach ($name in $wikiPages.Keys) {
$file = Join-Path -Path $destinationRoot -ChildPath "$name.md"
if ($PSCmdlet.ShouldProcess($file, 'Write wiki page')) {
[IO.File]::WriteAllText($file, $wikiPages[$name].TrimEnd() + "`n", $encoding)
}
}

79
.github/scripts/Invoke-Tests.ps1

@ -0,0 +1,79 @@
<#
.SYNOPSIS
Runs the Pester tests in the Tests folder and reports the result to GitHub Actions.
.DESCRIPTION
Imports Pester 5.7.1, runs the tests against the module build in NTFSSecurity\bin\Release, writes the result
file in the NUnit format, and adds the counts and the failed tests to the job summary of GitHub Actions. Fails if
a test or a test file fails.
.PARAMETER ResultPath
Specifies the path of the result file.
.PARAMETER Title
Specifies the heading of the test results in the job summary, such as the PowerShell edition.
.EXAMPLE
.\.github\scripts\Invoke-Tests.ps1 -ResultPath TestResults\WindowsPowerShell.xml -Title 'Windows PowerShell 5.1'
Runs the tests and writes the result file. Outside GitHub Actions, the script writes no job summary.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$ResultPath,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$Title
)
$ErrorActionPreference = 'Stop'
Import-Module -Name Pester -RequiredVersion 5.7.1
$resultFolder = Split-Path -Path $ResultPath -Parent
if ($resultFolder -and -not (Test-Path -LiteralPath $resultFolder)) {
New-Item -ItemType Directory -Path $resultFolder | Out-Null
}
$configuration = New-PesterConfiguration
$configuration.Run.Path = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Tests'
$configuration.Run.PassThru = $true
$configuration.Output.Verbosity = 'Detailed'
$configuration.TestResult.Enabled = $true
$configuration.TestResult.OutputFormat = 'NUnitXml'
$configuration.TestResult.OutputPath = $ResultPath
$result = Invoke-Pester -Configuration $configuration
if ($env:GITHUB_STEP_SUMMARY) {
$summary = New-Object -TypeName 'System.Collections.Generic.List[string]'
$summary.Add("### Tests in $Title")
$summary.Add('')
$summary.Add('| Result | Passed | Failed | Skipped | Total |')
$summary.Add('| --- | ---: | ---: | ---: | ---: |')
$summary.Add(('| {0} | {1} | {2} | {3} | {4} |' -f $result.Result, $result.PassedCount, $result.FailedCount,
$result.SkippedCount, $result.TotalCount))
if ($result.Failed.Count -gt 0 -or $result.FailedContainersCount -gt 0) {
$summary.Add('')
$summary.Add('Failed:')
$summary.Add('')
foreach ($test in $result.Failed) {
$message = "$(@($test.ErrorRecord)[0])" -replace '\s+', ' '
$summary.Add(('- {0}: {1}' -f $test.ExpandedPath, $message))
}
foreach ($container in $result.Containers | Where-Object -Property Result -EQ -Value 'Failed') {
$summary.Add(('- {0}: {1}' -f $container.Item, ("$(@($container.ErrorRecord)[0])" -replace '\s+', ' ')))
}
}
$summary.Add('')
$encoding = New-Object -TypeName 'System.Text.UTF8Encoding' -ArgumentList $false
[IO.File]::AppendAllText($env:GITHUB_STEP_SUMMARY, ($summary -join "`n") + "`n", $encoding)
}
if ($result.Result -ne 'Passed') {
throw "The tests in $Title failed: $($result.FailedCount) failed tests, $($result.FailedContainersCount) failed test files."
}

181
.github/workflows/ci.yml

@ -0,0 +1,181 @@
# 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.
name: CI
on:
pull_request:
push:
branches:
- master
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build:
name: Build and test
runs-on: windows-2025
timeout-minutes: 30
steps:
- name: Keep Windows line endings in the working tree
run: git config --global core.autocrlf true
- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install platyPS, MarkdownLinkCheck, and Pester
shell: powershell
run: |
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
# Installed for all users, so that PowerShell 7 finds the modules, too
Install-Module -Name platyPS -RequiredVersion 0.14.2 -Scope AllUsers -Force
Install-Module -Name MarkdownLinkCheck -RequiredVersion 0.2.0 -Scope AllUsers -Force
# The image includes Pester 3.4.0, which is signed by a different publisher.
Install-Module -Name Pester -RequiredVersion 5.7.1 -Scope AllUsers -Force -SkipPublisherCheck
- name: Restore the NuGet packages
shell: powershell
run: |
nuget restore NTFSSecurity\packages.config -PackagesDirectory packages -NonInteractive
if ($LASTEXITCODE -ne 0) {
throw "NuGet failed with exit code $LASTEXITCODE."
}
nuget restore Security2\packages.config -PackagesDirectory packages -NonInteractive
if ($LASTEXITCODE -ne 0) {
throw "NuGet failed with exit code $LASTEXITCODE."
}
# Provides the .NET Framework 4.5.2 reference assemblies, so the build does
# not depend on a targeting pack installed on the runner.
nuget install Microsoft.NETFramework.ReferenceAssemblies.net452 -Version 1.0.3 -OutputDirectory packages -NonInteractive
if ($LASTEXITCODE -ne 0) {
throw "NuGet failed with exit code $LASTEXITCODE."
}
- name: Build the module
id: build
shell: powershell
run: |
# MSBuild isn't on the path of the runner; vswhere finds the one of Visual Studio.
$vswhere = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath 'Microsoft Visual Studio\Installer\vswhere.exe'
$msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe' | Select-Object -First 1
if (-not $msbuild) {
throw 'MSBuild was not found.'
}
$referenceAssemblies = "$env:GITHUB_WORKSPACE\packages\Microsoft.NETFramework.ReferenceAssemblies.net452.1.0.3\build"
& $msbuild NTFSSecurity\NTFSSecurity.csproj /nologo /verbosity:minimal /p:Configuration=Release "/p:TargetFrameworkRootPath=$referenceAssemblies" "/p:FrameworkPathOverride=$referenceAssemblies\.NETFramework\v4.5.2"
if ($LASTEXITCODE -ne 0) {
throw "MSBuild failed with exit code $LASTEXITCODE."
}
- name: Check the documentation against the build
shell: powershell
run: |
Import-Module -Name platyPS -RequiredVersion 0.14.2
Import-Module -Name MarkdownLinkCheck -RequiredVersion 0.2.0
Import-Module -Name .\NTFSSecurity\bin\Release\NTFSSecurity.psd1 -Force
# 01. Test that the documentation matches the cmdlets built from source
Update-MarkdownHelp -Path ./Docs/Cmdlets | Out-Null
$diff = git diff -- Docs/Cmdlets
if ($diff) {
throw "Help is not up-to-date, run Update-MarkdownHelp: $diff"
}
# 02. Verify hyperlinks
$brokenLinks = Get-MarkdownLink -Path .\Docs\ -BrokenOnly
if ($brokenLinks) {
throw "Found broken hyperlinks $brokenLinks"
}
# 03. Test that the help file of the module matches the documentation
New-ExternalHelp -Path ./Docs/Cmdlets -OutputPath ./NTFSSecurity/en-US -Force | Out-Null
$helpChanges = git status --porcelain -- NTFSSecurity/en-US
if ($helpChanges) {
throw "The help file is not up-to-date, run New-ExternalHelp -Path .\Docs\Cmdlets -OutputPath .\NTFSSecurity\en-US -Force: $helpChanges"
}
# 04. Run the Pester tests against the build, also when the documentation check failed
- name: Run the tests in Windows PowerShell 5.1
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
shell: powershell
run: .\.github\scripts\Invoke-Tests.ps1 -ResultPath TestResults\WindowsPowerShell.xml -Title 'Windows PowerShell 5.1'
- name: Run the tests in PowerShell 7
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
shell: pwsh
run: ./.github/scripts/Invoke-Tests.ps1 -ResultPath TestResults/PowerShell7.xml -Title 'PowerShell 7'
- name: Upload the test results
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-results
path: TestResults/
if-no-files-found: ignore
wiki:
name: Wiki
needs: build
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.
permissions:
contents: write
steps:
- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Generate the wiki pages
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
gh auth setup-git
$wiki = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'wiki'
git clone --quiet --depth 1 "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY.wiki.git" $wiki
if ($LASTEXITCODE -ne 0) {
throw "Cloning the wiki failed with exit code $LASTEXITCODE."
}
./.github/scripts/Export-WikiContent.ps1 -Path ./Docs -DestinationPath $wiki -RepositoryUrl "$env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY"
git -C $wiki add --all
$changes = @(git -C $wiki diff --cached --name-status)
$summary = if ($changes) {
@('### Wiki', '', 'Changed pages (A added, M modified, D deleted):', '', '```text') + $changes + @('```')
} else {
@('### Wiki', '', 'The wiki is up to date.')
}
Add-Content -LiteralPath $env:GITHUB_STEP_SUMMARY -Value $summary
Add-Content -LiteralPath $env:GITHUB_ENV -Value "WIKI_PATH=$wiki"
- name: Publish the wiki
if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
git -C $env:WIKI_PATH diff --cached --quiet
if ($LASTEXITCODE -eq 0) {
'The wiki is up to date.'
exit 0
}
git -C $env:WIKI_PATH -c user.name='github-actions[bot]' -c user.email='41898282+github-actions[bot]@users.noreply.github.com' commit --quiet --message "Update from $env:GITHUB_SHA"
if ($LASTEXITCODE -ne 0) {
throw "Committing the wiki failed with exit code $LASTEXITCODE."
}
git -C $env:WIKI_PATH push --quiet
if ($LASTEXITCODE -ne 0) {
throw "Publishing the wiki failed with exit code $LASTEXITCODE."
}

46
.memory-bank/activeContext.md

@ -9,37 +9,31 @@ source: current task evidence
## Current focus ## Current focus
Work packages 3 and 4 are PR-ready and committed locally, not pushed: PRs #94 (docs on GitHub) and #95 (manifest and version 5.0.0, stacked on
`ai/docs-on-github` (docs on GitHub, wiki retired, Decision 9) and PR #94) are open and build on AppVeyor. The move to GitHub Actions (CI and
`ai/manifest-version` (valid manifest, version 5.0.0, Decision 10), a wiki generated from `Docs`, Decision 11) is PR-ready on the local branch
stacked on it. The maintainer pushes both and opens the PRs; the PR `ai/github-actions`, stacked on #95; the maintainer pushes it and opens the
descriptions are in the session files. Merge the docs PR first with a PR. Merge order: #94, #95 (merge commits), then the GitHub Actions PR.
merge commit.
## Evidence ## Evidence
- All 256 relative links in `Docs`, `README.md`, and `CHANGELOG.md` resolve, - `Wiki.Tests.ps1` failed 25 of 25 before the exporter existed and passes
including 5 anchors checked against GitHub's slug rules; MarkdownLinkCheck 25 of 25 in Windows PowerShell 5.1 and PowerShell 7.6.1. A negative
0.2.0 (CI step 02) finds 0 broken links in `Docs` in Windows PowerShell control (broken anchor, missing page, missing file) reports all three.
5.1; markdownlint reports 0 issues in the changed pages. - A dry run against a clone of the live wiki adds 41 pages, changes Home
- The documented manual install (`Unblock-File`, `Expand-Archive` into and How-to-install, and deletes the stale `Cmdlets.md` and
`$env:ProgramFiles\WindowsPowerShell\Modules`) was tested with the 4.2.6 `Version-History.textile`; nothing was pushed.
zip in a `$env:TEMP` sandbox: the module imports under `RemoteSigned`. - `Invoke-Tests.ps1`, as CI calls it, passed locally against a Release
`Expand-Archive` doesn't pass the download mark on; File Explorer's zip build: Windows PowerShell 5.1 253 of 253; PowerShell 7 217 passed and 36
handler does, and the import then fails. skipped (`Get-Help -Online` runs only in Windows PowerShell).
- The wiki stopped at 4.2.4; the Gallery has 4.2.5 (2019-07-11) and 4.2.6 - actionlint 1.7.12 reports nothing for `.github/workflows/ci.yml`. The
(2019-07-12), whose notes were reconstructed from `4.2.4..4.2.6`. runner image `windows-2025` has Visual Studio 2022 MSBuild 17.14, NuGet,
the GitHub CLI, and PowerShell 7.6; `master` has no branch protection.
- The local branch `ai/read-the-docs` keeps the dropped strict-build work - The local branch `ai/read-the-docs` keeps the dropped strict-build work
(`886c874`, `325ec76`); delete it once it is no longer wanted. (`886c874`, `325ec76`); delete it once it is no longer wanted.
- Work package 4 test first: against the previous build, all 10 new tests
failed for the expected reasons; after the change, the AppVeyor test
script run locally in Windows PowerShell 5.1 passed (228 of 228 Pester
tests), and the new tests pass in PowerShell 7.6.1 (10 of 10).
- The local Release build (`packages\`, `bin\`, `obj\`) is deleted after
the work; rebuild from the NuGet cache (`techContext.md`).
## Next step ## Next step
After the maintainer pushes: read the AppVeyor results of both PRs through After the maintainer pushes: read the AppVeyor results of #94 and #95 and
the REST API. Work package 5 (code defects) starts only after the the first GitHub Actions run with `gh run view`. Work package 5 (code
maintainer's go-ahead. defects) starts only after the maintainer's go-ahead.

5
.memory-bank/decisions/0006-ci-checks-docs-against-build.md

@ -1,14 +1,15 @@
--- ---
status: accepted status: accepted
date: 2026-10-02 date: 2026-10-02
last-verified: 2026-10-02 last-verified: 2026-10-04
owner: shared owner: shared
source: PR #91 (moved from systemPatterns.md) source: PR #91 (moved from systemPatterns.md)
--- ---
# Decision 6: CI checks the docs against a build of the source # Decision 6: CI checks the docs against a build of the source
- Choice: `appveyor.yml` builds `NTFSSecurity.csproj` and runs - Choice: The CI workflow (`.github/workflows/ci.yml`, Decision 11; before
that `appveyor.yml`) builds `NTFSSecurity.csproj` and runs
`Update-MarkdownHelp` against `NTFSSecurity\bin\Release`, not against the `Update-MarkdownHelp` against `NTFSSecurity\bin\Release`, not against the
module from the PowerShell Gallery. module from the PowerShell Gallery.
- Rationale: Checking against the last release fails for every unreleased - Rationale: Checking against the last release fails for every unreleased

7
.memory-bank/decisions/0008-commit-generated-help.md

@ -1,7 +1,7 @@
--- ---
status: accepted status: accepted
date: 2026-10-02 date: 2026-10-02
last-verified: 2026-10-02 last-verified: 2026-10-04
owner: shared owner: shared
source: maintainer choice in work package 2 (option A) source: maintainer choice in work package 2 (option A)
--- ---
@ -11,8 +11,9 @@ source: maintainer choice in work package 2 (option A)
- Choice: `NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml` is generated from - Choice: `NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml` is generated from
`Docs/Cmdlets` with `New-ExternalHelp` (platyPS 0.14.2, Windows `Docs/Cmdlets` with `New-ExternalHelp` (platyPS 0.14.2, Windows
PowerShell 5.1) and committed. `NTFSSecurity.csproj` copies it to the PowerShell 5.1) and committed. `NTFSSecurity.csproj` copies it to the
output as `Content`, the manifest `FileList` lists it, and `appveyor.yml` output as `Content`, the manifest `FileList` lists it, and the CI workflow
regenerates it and fails on `git status --porcelain -- NTFSSecurity/en-US`. (`.github/workflows/ci.yml`) regenerates it and fails on
`git status --porcelain -- NTFSSecurity/en-US`.
- Rationale: Releases are built locally in Visual Studio (Debug, written to - Rationale: Releases are built locally in Visual Studio (Debug, written to
`C:\Program Files\WindowsPowerShell\Modules\NTFSSecurity`) and published `C:\Program Files\WindowsPowerShell\Modules\NTFSSecurity`) and published
by hand with `Publish-Module`. A committed file ships with every build by hand with `Publish-Module`. A committed file ships with every build

13
.memory-bank/decisions/0009-docs-on-github.md

@ -10,16 +10,17 @@ source: maintainer decision in work package 3
- Choice: The documentation lives in `Docs` and `README.md`, and GitHub - Choice: The documentation lives in `Docs` and `README.md`, and GitHub
renders it. There is no documentation site: the Read the Docs and MkDocs renders it. There is no documentation site: the Read the Docs and MkDocs
configuration is removed. The GitHub wiki is retired: its version history configuration is removed. The wiki's version history and installation
and installation steps moved to `Docs/Version-History.md` and steps moved to `Docs/Version-History.md` and `Docs/README.md`; the wiki
`Docs/README.md`, and the maintainer turns the wiki off. itself is now generated from `Docs` (Decision 11) instead of being turned
off.
- Rationale: One source of truth that is versioned with the code, reviewed - Rationale: One source of truth that is versioned with the code, reviewed
in pull requests, and checked by AppVeyor. The Read the Docs project in pull requests, and checked by CI. The Read the Docs project
`ntfssecurity` belongs to `Sup3rlativ3` and points to a fork that no `ntfssecurity` belongs to `Sup3rlativ3` and points to a fork that no
longer exists; a wiki is edited outside pull requests and CI. longer exists; a hand-written wiki is edited outside pull requests and CI.
- Consequences: `online version` links stay on GitHub (Decision 4). Section - Consequences: `online version` links stay on GitHub (Decision 4). Section
anchors follow GitHub's rules. `Docs/index.md` became `Docs/README.md`, so anchors follow GitHub's rules. `Docs/index.md` became `Docs/README.md`, so
that GitHub shows it when you open the `Docs` folder. that GitHub shows it when you open the `Docs` folder.
- Rejected: taking over or re-importing the Read the Docs project with a - Rejected: taking over or re-importing the Read the Docs project with a
strict MkDocs build (prepared on the local branch `ai/read-the-docs`, not strict MkDocs build (prepared on the local branch `ai/read-the-docs`, not
merged), and publishing `Docs` to the wiki. merged).

27
.memory-bank/decisions/0011-github-actions.md

@ -0,0 +1,27 @@
---
status: accepted
date: 2026-10-04
last-verified: 2026-10-04
owner: shared
source: maintainer decision after work package 4
---
# Decision 11: CI and the wiki run on GitHub Actions
- Choice: `.github/workflows/ci.yml` replaces AppVeyor. On pull requests and
pushes to `master`, the `build` job (`windows-2025`) builds the module in
Release, checks the docs against the build, and runs the Pester tests in
Windows PowerShell 5.1 and PowerShell 7. The `wiki` job converts `Docs`
with `.github/scripts/Export-WikiContent.ps1` and publishes the wiki from
`master` with the built-in token; on pull requests it lists the pages that
would change. `appveyor.yml` is removed.
- Rationale: The maintainer wants a browsable wiki without a second, hand-
written copy of the docs (the 2018 wiki went stale), and one CI platform
instead of two. Public repositories get Windows runners for free, and the
checks appear on the pull request without a third-party service.
- Consequences: `Docs` stays the only source (Decision 9); the wiki is a
generated mirror, and edits made in the wiki are overwritten. Only the
`wiki` job has `contents: write`; actions are pinned by commit SHA. Test
results appear in the job summary and as the `test-results` artifact.
- Rejected: a hand-maintained wiki next to `Docs`, publishing the wiki by
hand at release time, and keeping AppVeyor for build and tests.

43
.memory-bank/progress.md

@ -13,8 +13,9 @@ PRs #91, #92, and #93 are merged. The documentation matches the cmdlets at
`master`, and the module ships the help file generated from it `master`, and the module ships the help file generated from it
(`en-US\NTFSSecurity.dll-Help.xml`). Otherwise the module source differs (`en-US\NTFSSecurity.dll-Help.xml`). Otherwise the module source differs
from the 4.2.6 release only by the `Remove-Item2 -PassThru` rename and from the 4.2.6 release only by the `Remove-Item2 -PassThru` rename and
`CompatiblePSEditions`. Work packages 3 (`ai/docs-on-github`) and 4 `CompatiblePSEditions`. Open PRs: #94 (work package 3) and #95 (work package
(`ai/manifest-version`, stacked on 3) are PR-ready locally. 4, stacked on #94); the move to GitHub Actions is PR-ready locally on
`ai/github-actions`, stacked on #95.
## Recent milestones ## Recent milestones
@ -46,6 +47,13 @@ from the 4.2.6 release only by the `Remove-Item2 -PassThru` rename and
PowerShell 5.1, passed: no docs drift, 0 broken links, current help file, PowerShell 5.1, passed: no docs drift, 0 broken links, current help file,
228 of 228 Pester tests. `Test-ModuleManifest` passes in Windows 228 of 228 Pester tests. `Test-ModuleManifest` passes in Windows
PowerShell 5.1 and PowerShell 7.6.1. PowerShell 5.1 and PowerShell 7.6.1.
- 2026-10-04: The maintainer opened #94 and #95, then chose to keep the wiki,
generated from `Docs`, and to move CI from AppVeyor to GitHub Actions in
one PR (Decision 11): committed locally on `ai/github-actions`, stacked on
#95. `Wiki.Tests.ps1` failed 25 of 25 before the exporter existed and
passes in both editions; `Invoke-Tests.ps1` passed locally in Windows
PowerShell 5.1 (253 of 253) and PowerShell 7 (217 passed, 36 skipped);
actionlint found nothing.
## Stable capabilities ## Stable capabilities
@ -64,15 +72,15 @@ next package starts only after the maintainer's go-ahead.
1. Housekeeping: done (#92). 1. Housekeeping: done (#92).
2. Ship help: done (#93). 2. Ship help: done (#93).
3. Docs on GitHub (was: Read the Docs), PR-ready on `ai/docs-on-github`: 3. Docs on GitHub (was: Read the Docs), PR #94: Read the Docs and MkDocs
Read the Docs and MkDocs configuration removed, `Docs/index.md` renamed configuration removed, `Docs/index.md` renamed to `Docs/README.md`, the
to `Docs/README.md`, the wiki's version history and install steps moved wiki's version history and install steps moved into `Docs` (with
into `Docs` (with reconstructed notes for 4.2.5 and 4.2.6), contributor reconstructed notes for 4.2.5 and 4.2.6), contributor guide updated. Keep
guide updated. After the merge, the maintainer turns the wiki off, points the wiki on: the GitHub Actions PR generates it from `Docs`, which also
the notes of releases 4.2.4 and 4.2.6 to `Docs/Version-History.md`, and keeps the release-note links to `wiki/Version-History` working.
may ask `Sup3rlativ3` to delete the Read the Docs project. `Sup3rlativ3` may be asked to delete the Read the Docs project.
4. Manifest and version, PR-ready on `ai/manifest-version`, stacked on 4. Manifest and version, PR #95, stacked on #94 (merge #94 first with a
`ai/docs-on-github` (merge that PR first with a merge commit). merge commit).
Maintainer decisions: `PowerShellVersion` 5.1, `DotNetFrameworkVersion` Maintainer decisions: `PowerShellVersion` 5.1, `DotNetFrameworkVersion`
4.5.2, `RootModule`; version 5.0.0; `[Alias('PassThur')]` on 4.5.2, `RootModule`; version 5.0.0; `[Alias('PassThur')]` on
`Remove-Item2 -PassThru`, listed under Deprecated; `NTFSSecurity`, `Remove-Item2 -PassThru`, listed under Deprecated; `NTFSSecurity`,
@ -84,12 +92,19 @@ next package starts only after the maintainer's go-ahead.
the whole output folder (`.pdb`, `.xml`, the whole output folder (`.pdb`, `.xml`,
`System.Management.Automation.dll`), and the removed `System.Management.Automation.dll`), and the removed
`NTFSSecurity-Help.xml` would ship again. `NTFSSecurity-Help.xml` would ship again.
4b. GitHub Actions for CI and the wiki (Decision 11), PR-ready on
`ai/github-actions`, stacked on #95 (merge #94 and #95 first with merge
commits). AppVeyor reports a failure on this PR because it removes
`appveyor.yml`; that's expected. After the merge: delete the AppVeyor
project `raandree/ntfssecurity` and revoke AppVeyor's GitHub access,
check the first wiki publication, and consider **Restrict editing to
collaborators only** for the wiki.
5. Code defects, listed below: `review: on`, one PR per group, regression 5. Code defects, listed below: `review: on`, one PR per group, regression
test first. Pester 5 tests import `NTFSSecurity\bin\Release`, run in a test first. Pester 5 tests import `NTFSSecurity\bin\Release`, run in a
`$env:TEMP` sandbox and in `appveyor.yml` (pattern: `$env:TEMP` sandbox and in the CI workflow (pattern:
`Tests\Help.Tests.ps1`), and skip elevated cases when not elevated; `Tests\Help.Tests.ps1`), and skip elevated cases when not elevated;
check whether AppVeyor runs elevated. Each fix updates its cmdlet page check whether the GitHub Actions Windows runner runs elevated. Each fix
and `CHANGELOG.md`. updates its cmdlet page and `CHANGELOG.md`.
### Code defects (work package 5) ### Code defects (work package 5)

2
.memory-bank/projectbrief.md

@ -38,7 +38,7 @@ Source: `README.md`, `NTFSSecurity/NTFSSecurity.psd1`.
1. Every exported cmdlet has an accurate platyPS page in `Docs/Cmdlets`. 1. Every exported cmdlet has an accurate platyPS page in `Docs/Cmdlets`.
2. `Update-MarkdownHelp` against the module produces no parameter drift 2. `Update-MarkdownHelp` against the module produces no parameter drift
(the check in `appveyor.yml`). (the check in `.github/workflows/ci.yml`).
3. The module imports in Windows PowerShell 5.1 and PowerShell 7 3. The module imports in Windows PowerShell 5.1 and PowerShell 7
(`CompatiblePSEditions = 'Core', 'Desktop'`). (`CompatiblePSEditions = 'Core', 'Desktop'`).
4. Further release criteria: To confirm. 4. Further release criteria: To confirm.

28
.memory-bank/systemPatterns.md

@ -57,6 +57,7 @@ Each Decision record is a file in `decisions/`; read only the relevant ones.
| 8 | [Commit the generated help file and check it in CI](decisions/0008-commit-generated-help.md) | | 8 | [Commit the generated help file and check it in CI](decisions/0008-commit-generated-help.md) |
| 9 | [Keep the documentation on GitHub](decisions/0009-docs-on-github.md) | | 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) | | 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) |
## Patterns ## Patterns
@ -64,10 +65,16 @@ Each Decision record is a file in `decisions/`; read only the relevant ones.
- Run platyPS in Windows PowerShell 5.1 against a module build; a copy of - Run platyPS in Windows PowerShell 5.1 against a module build; a copy of
`Docs/Cmdlets` must round-trip through `Update-MarkdownHelp` unchanged. `Docs/Cmdlets` must round-trip through `Update-MarkdownHelp` unchanged.
- GitHub renders the docs (Decision 9). AppVeyor's link check covers only - GitHub renders the docs (Decision 9), and CI publishes them to the wiki
relative links in `Docs` and ignores anchors, so check anchors against (Decision 11). The MarkdownLinkCheck step covers only relative links in
GitHub's slug rules (lowercase, punctuation removed, spaces to hyphens) `Docs` and ignores anchors; `Tests\Wiki.Tests.ps1` checks every link of
and the links in `README.md` and `CHANGELOG.md` separately. the generated wiki, including anchors (GitHub's slug rules: lowercase,
punctuation removed, spaces to hyphens). Check the links in `README.md`
and `CHANGELOG.md` separately.
- The wiki is generated: edit `Docs`, never the wiki.
`Export-WikiContent.ps1` names a page after its file (`Docs/README.md`
becomes Home), rewrites links, and builds the sidebar from the cmdlet
groups of `Docs/README.md`; a cmdlet missing there fails `Wiki.Tests.ps1`.
- platyPS rewrites non-ASCII punctuation such as em dashes; keep cmdlet pages - platyPS rewrites non-ASCII punctuation such as em dashes; keep cmdlet pages
ASCII-only. ASCII-only.
- In cmdlet pages, end a sentence with a link: platyPS renders a link as - In cmdlet pages, end a sentence with a link: platyPS renders a link as
@ -78,15 +85,16 @@ Each Decision record is a file in `decisions/`; read only the relevant ones.
### Testing the module ### Testing the module
- Pester 5 tests in `Tests/*.Tests.ps1` import - Pester 5 tests in `Tests/*.Tests.ps1` import
`NTFSSecurity\bin\Release\NTFSSecurity.psd1` and run in Windows `NTFSSecurity\bin\Release\NTFSSecurity.psd1`; CI runs them in Windows
PowerShell 5.1 (as in AppVeyor); PowerShell 7 runs are a secondary check. PowerShell 5.1 and in PowerShell 7 (Decision 11).
- `Get-Help -Online` is tested with the internal test hook - `Get-Help -Online` is tested with the internal test hook
`BypassOnlineHelpRetrieval`, which returns the URI instead of opening a `BypassOnlineHelpRetrieval`, which returns the URI instead of opening a
browser. In PowerShell 7 the hook also skips the help file, so that test browser. In PowerShell 7 the hook also skips the help file, so that test
runs only in Windows PowerShell; PowerShell 7 resolves the same URI. runs only in Windows PowerShell (36 skipped tests in PowerShell 7);
- Report Pester 5 results to AppVeyor through the build worker API, not as PowerShell 7 resolves the same URI.
an uploaded NUnit file: the NUnit import files each test under every - `.github/scripts/Invoke-Tests.ps1` runs Pester for CI: the counts and the
enclosing block (870 entries for 218 tests in build 54834154). 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` - `Tests\Manifest.Tests.ps1` checks the built manifest: `Test-ModuleManifest`
without errors or warnings, exactly 36 cmdlets, and one version without errors or warnings, exactly 36 cmdlets, and one version
(Decision 10). Add a new cmdlet to `CmdletsToExport` and to the expected (Decision 10). Add a new cmdlet to `CmdletsToExport` and to the expected

71
.memory-bank/techContext.md

@ -19,15 +19,17 @@ source: repository evidence
- Module: `NTFSSecurity.psd1` loads `NTFSSecurity.psm1` (aliases `dir2`, - Module: `NTFSSecurity.psd1` loads `NTFSSecurity.psm1` (aliases `dir2`,
`gi2`, `rm2`, `del2`), `NTFSSecurity.Init.ps1` (Add-Type of the helper `gi2`, `rm2`, `del2`), `NTFSSecurity.Init.ps1` (Add-Type of the helper
assemblies, prepends `NTFSSecurity.format.ps1xml`), and `NTFSSecurity.dll`. assemblies, prepends `NTFSSecurity.format.ps1xml`), and `NTFSSecurity.dll`.
- Documentation: Markdown in `Docs` and `README.md`, rendered by GitHub; no - Documentation: Markdown in `Docs` and `README.md`, rendered by GitHub and
documentation site and no wiki (Decision 9). Cmdlet pages are platyPS published to the wiki by CI; no documentation site (Decisions 9 and 11).
0.14 markdown (schema 2.0.0) in `Docs/Cmdlets`. Cmdlet pages are platyPS 0.14 markdown (schema 2.0.0) in `Docs/Cmdlets`.
- Help: `NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml`, generated from - Help: `NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml`, generated from
`Docs/Cmdlets` and committed (Decision 8). `Docs/Cmdlets` and committed (Decision 8).
- Tests: Pester 5 tests in `Tests` against the Release build: - Tests: Pester 5 tests in `Tests`: `Help.Tests.ps1` (help of every
`Help.Tests.ps1` (help of every cmdlet), `Manifest.Tests.ps1` (manifest cmdlet), `Manifest.Tests.ps1` (manifest and versions, Decision 10), and
and versions, Decision 10), and `Remove-Item2.Tests.ps1` (`-PassThur` `Remove-Item2.Tests.ps1` (`-PassThur` alias) against the Release build;
alias). `Wiki.Tests.ps1` (wiki conversion) without a build.
- CI: GitHub Actions, `.github/workflows/ci.yml` with the scripts in
`.github/scripts` (Decision 11).
## Environment ## Environment
@ -79,12 +81,13 @@ source: repository evidence
(`.pdb`, `AlphaFS.xml`, 7 MB `System.Management.Automation.dll`), and the (`.pdb`, `AlphaFS.xml`, 7 MB `System.Management.Automation.dll`), and the
published manifest differs from the tag only by `ModuleVersion` (tags published manifest differs from the tag only by `ModuleVersion` (tags
carry the previous version). GitHub releases attach `NTFSSecurity.zip`. carry the previous version). GitHub releases attach `NTFSSecurity.zip`.
- CI: AppVeyor project `raandree/ntfssecurity` builds branches and pull - CI: GitHub Actions on pull requests and pushes to `master` (Decision 11).
requests. The Read the Docs project `ntfssecurity` (maintainer The AppVeyor project `raandree/ntfssecurity` builds until the maintainer
`Sup3rlativ3`) and a second AppVeyor project are attached to the fork deletes it after the GitHub Actions PR is merged. The Read the Docs
`Sup3rlativ3/NTFSSecurity`, which no longer exists (GitHub 404, project `ntfssecurity` (maintainer `Sup3rlativ3`) and a second AppVeyor
2026-10-04). That site still serves pages from 2020 and isn't used project are attached to the fork `Sup3rlativ3/NTFSSecurity`, which no
(Decision 9). longer exists (GitHub 404, 2026-10-04). That site still serves pages from
2020 and isn't used (Decision 9).
- `Get-FileHash2` fails in PowerShell 7; all other cmdlets passed a smoke - `Get-FileHash2` fails in PowerShell 7; all other cmdlets passed a smoke
test in PowerShell 7.6. test in PowerShell 7.6.
- `CHANGELOG.md` lists user-visible changes only; CI and build-only changes - `CHANGELOG.md` lists user-visible changes only; CI and build-only changes
@ -102,20 +105,28 @@ source: repository evidence
## Validation ## Validation
- CI (`appveyor.yml`, image Visual Studio 2022): restore `packages.config` - CI (`.github/workflows/ci.yml`): job `build` on `windows-2025` installs
per project plus `Microsoft.NETFramework.ReferenceAssemblies.net452` platyPS 0.14.2, MarkdownLinkCheck 0.2.0, and Pester 5.7.1 for all users,
1.0.3, build `NTFSSecurity.csproj` in Release with restores `packages.config` per project plus
`TargetFrameworkRootPath`/`FrameworkPathOverride`, import `Microsoft.NETFramework.ReferenceAssemblies.net452` 1.0.3, builds
`NTFSSecurity\bin\Release\NTFSSecurity.psd1`, then: 01 run `NTFSSecurity.csproj` in Release with the MSBuild that `vswhere` finds,
`Update-MarkdownHelp` and fail on `git diff -- Docs/Cmdlets`; 02 then: 01 `Update-MarkdownHelp` and fail on `git diff -- Docs/Cmdlets`; 02
`Get-MarkdownLink -BrokenOnly`; 03 regenerate the help file and fail on `Get-MarkdownLink -BrokenOnly`; 03 regenerate the help file and fail on
`git status --porcelain -- NTFSSecurity/en-US`; 04 Pester 5.7.1 on `git status --porcelain -- NTFSSecurity/en-US`; 04 `Invoke-Tests.ps1` in
`Tests`, each result reported once through the build worker API Windows PowerShell 5.1 and in PowerShell 7. Job `wiki` on `ubuntu-latest`
(`POST $env:APPVEYOR_API_URL/api/tests/batch`). clones the wiki (`gh auth setup-git` with the built-in token), runs
- AppVeyor REST API (public, no token): build `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.
- Read CI runs with `gh run list --repo raandree/NTFSSecurity --workflow
ci.yml` and `gh run view <id> --log-failed` (read-only). While AppVeyor
still builds: `api/projects/raandree/ntfssecurity/history`, build
`api/projects/raandree/ntfssecurity/builds/<buildId>`, job log `api/projects/raandree/ntfssecurity/builds/<buildId>`, job log
`api/buildjobs/<jobId>/log` (bytes; decode as UTF-8), and test list `api/buildjobs/<jobId>/log` (public, no token).
`api/buildjobs/<jobId>/tests`. - Workflow lint: actionlint (download the release zip into `$env:TEMP` and
check its SHA-256 against the checksum file); PowerShell steps check
`$LASTEXITCODE` after every native command, because GitHub checks only
the last one.
- Run platyPS in Windows PowerShell 5.1 to avoid PowerShell 7.4+ - Run platyPS in Windows PowerShell 5.1 to avoid PowerShell 7.4+
`-ProgressAction` noise. `-ProgressAction` noise.
- Placeholder check: no `{{` left in `Docs/Cmdlets/*.md`. - Placeholder check: no `{{` left in `Docs/Cmdlets/*.md`.
@ -127,8 +138,8 @@ source: repository evidence
path. A run without `bin\Release\en-US` must fail. path. A run without `bin\Release\en-US` must fail.
- Markdown lint: `npx markdownlint-cli2` with `MD013` limited to prose - Markdown lint: `npx markdownlint-cli2` with `MD013` limited to prose
(tables, code, and headings excluded) on the conceptual pages. (tables, code, and headings excluded) on the conceptual pages.
- YAML: `ConvertFrom-Yaml` (powershell-yaml) on `appveyor.yml`. - YAML: `ConvertFrom-Yaml` (powershell-yaml) on `.github/workflows/ci.yml`.
- Links: AppVeyor step 02 (MarkdownLinkCheck 0.2.0) checks only relative - Links: the CI step 02 (MarkdownLinkCheck 0.2.0) checks only relative
links in `Docs`; it strips anchors and skips absolute URLs. Check anchors links in `Docs`; it strips anchors and skips absolute URLs.
against GitHub's heading slugs, and the links in `README.md` and `Wiki.Tests.ps1` checks the wiki links with their anchors; check the
`CHANGELOG.md`, with a script. links in `README.md` and `CHANGELOG.md` with a script.

6
CHANGELOG.md

@ -8,6 +8,12 @@ The format is based on
## [Unreleased] ## [Unreleased]
### Added
- 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
## [5.0.0] - 2026-10-04 ## [5.0.0] - 2026-10-04
### Changed ### Changed

48
Docs/Contributing/02-Writing.md

@ -1,8 +1,9 @@
# Write documentation # Write documentation
The NTFSSecurity documentation is written in Markdown and lives in the The NTFSSecurity documentation is written in Markdown and lives in the
`Docs` folder of the repository, where GitHub renders it. This page explains `Docs` folder of the repository, where GitHub renders it. GitHub Actions also
how the documentation is organized and how to change it. publishes it to the [wiki](https://github.com/raandree/NTFSSecurity/wiki).
This page explains how the documentation is organized and how to change it.
## Documentation structure ## Documentation structure
@ -17,6 +18,8 @@ how the documentation is organized and how to change it.
| `Docs/Contributing.md`, `Docs/Contributing/*.md` | This contributor guide | | `Docs/Contributing.md`, `Docs/Contributing/*.md` | This contributor guide |
| `README.md` | Front page of the GitHub repository | | `README.md` | Front page of the GitHub repository |
| `CHANGELOG.md` | Changes since 4.2.6 | | `CHANGELOG.md` | Changes since 4.2.6 |
| `.github/workflows/ci.yml` | CI build: documentation checks, tests, and wiki publishing |
| `.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, When you add a page, link to it from `Docs/README.md` or from a related page,
so that readers can find it. so that readers can find it.
@ -44,8 +47,9 @@ parameter descriptions, the examples, and the notes.
When a cmdlet changes, build the module, import the build output, and update When a cmdlet changes, build the module, import the build output, and update
the pages in Windows PowerShell 5.1. A Release build writes the module to the pages in Windows PowerShell 5.1. A Release build writes the module to
`NTFSSecurity\bin\Release`; the `before_build` and `build_script` steps in `NTFSSecurity\bin\Release`; the steps "Restore the NuGet packages" and "Build
`appveyor.yml` show the commands that the CI build uses: the module" in `.github/workflows/ci.yml` show the commands that the CI build
uses:
```powershell ```powershell
Install-Module -Name platyPS -RequiredVersion 0.14.2 Install-Module -Name platyPS -RequiredVersion 0.14.2
@ -83,19 +87,39 @@ before you push it, open it in Visual Studio Code and press **Ctrl+Shift+V**.
In a pull request, the **Files changed** tab shows a changed page rendered In a pull request, the **Files changed** tab shows a changed page rendered
when you open its menu (**...**) and select **View file**. when you open its menu (**...**) and select **View file**.
## Publish to the wiki
After every push to `master`, the CI workflow converts the pages in `Docs`,
except this contributor guide, into the pages of the
[wiki](https://github.com/raandree/NTFSSecurity/wiki) and publishes them.
`Docs/README.md` becomes the home page, every cmdlet page becomes a page with
the name of the cmdlet, and the sidebar lists the cmdlets in the groups of the
cmdlet list in `Docs/README.md`. Don't edit the wiki itself; the next push
overwrites it.
For a pull request, the summary of the CI run lists the wiki pages that would
change. To look at the pages before you push, write them into a clone of the
wiki:
```powershell
git clone https://github.com/raandree/NTFSSecurity.wiki.git $env:TEMP\wiki
.\.github\scripts\Export-WikiContent.ps1 -Path .\Docs -DestinationPath $env:TEMP\wiki
```
## Check your change ## Check your change
Before you open a pull request, check the following: Before you open a pull request, check the following:
- No page in `Docs/Cmdlets` contains a `{{ ... }}` placeholder. - No page in `Docs/Cmdlets` contains a `{{ ... }}` placeholder.
- `Update-MarkdownHelp` doesn't change any page in `Docs/Cmdlets`. The build - `Update-MarkdownHelp` doesn't change any page in `Docs/Cmdlets`. The CI
defined in `appveyor.yml` runs the same check. workflow runs the same check.
- `New-ExternalHelp` doesn't change - `New-ExternalHelp` doesn't change
`NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml`. The build runs the same `NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml`. The CI workflow runs the
check. same check.
- The Pester tests in `Tests` pass. They test the module in - The Pester tests in `Tests` pass. They test the module in
`NTFSSecurity\bin\Release`, for example that `Get-Help` shows every page. `NTFSSecurity\bin\Release`, for example that `Get-Help` shows every page,
The build runs them in Windows PowerShell 5.1 with Pester 5.7.1: and the conversion to the wiki. The CI workflow runs them in Windows
PowerShell 5.1 and in PowerShell 7 with Pester 5.7.1:
```powershell ```powershell
Install-Module -Name Pester -RequiredVersion 5.7.1 -SkipPublisherCheck Install-Module -Name Pester -RequiredVersion 5.7.1 -SkipPublisherCheck
@ -103,8 +127,8 @@ Before you open a pull request, check the following:
Invoke-Pester -Path .\Tests -Output Detailed Invoke-Pester -Path .\Tests -Output Detailed
``` ```
- All links work. The build checks them with `Get-MarkdownLink` from the - All links work. The CI workflow checks them with `Get-MarkdownLink` from
MarkdownLinkCheck module: the MarkdownLinkCheck module:
```powershell ```powershell
Get-MarkdownLink -Path .\Docs -BrokenOnly Get-MarkdownLink -Path .\Docs -BrokenOnly

4
Docs/Version-History.md

@ -1,8 +1,8 @@
# Version history # Version history
This page lists the changes in NTFSSecurity 4.2.6 and earlier. It replaces This page lists the changes in NTFSSecurity 4.2.6 and earlier. It replaces
the version history in the former GitHub wiki. For the changes since 4.2.6, the hand-written version history that the GitHub wiki kept until 2018. For the
see the [changelog](../CHANGELOG.md). changes since 4.2.6, see the [changelog](../CHANGELOG.md).
## 4.2.5 and 4.2.6 ## 4.2.5 and 4.2.6

4
README.md

@ -43,6 +43,10 @@ Get-ChildItem2 -Path C:\Data -Recurse | Get-NTFSAccess -ExcludeInherited
## Documentation ## Documentation
Read the documentation in the
[wiki](https://github.com/raandree/NTFSSecurity/wiki), which is generated
from the `Docs` folder, or in the folder itself:
- [Overview](Docs/README.md): features, requirements, and the list of cmdlets - [Overview](Docs/README.md): features, requirements, and the list of cmdlets
- [Concepts](Docs/Concepts.md): security descriptors, access rights, - [Concepts](Docs/Concepts.md): security descriptors, access rights,
inheritance, privileges, long paths, and module settings inheritance, privileges, long paths, and module settings

228
Tests/Wiki.Tests.ps1

@ -0,0 +1,228 @@
<#
Tests .github\scripts\Export-WikiContent.ps1, which converts the documentation in Docs into the pages of the
GitHub wiki: with a small sample of Docs for the conversion rules, and with the real Docs for complete pages and
working links.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeAll {
$exportScript = Join-Path -Path $PSScriptRoot -ChildPath '..\.github\scripts\Export-WikiContent.ps1'
function Get-GitHubAnchor {
# Returns the anchors that GitHub generates for the headings of a Markdown text.
param (
[Parameter(Mandatory)]
[string]
$Markdown
)
$text = [regex]::Replace($Markdown, '(?ms)^```.*?^```', '')
$seen = @{}
foreach ($match in [regex]::Matches($text, '(?m)^#{1,6}\s+(.+?)\s*#*\s*$')) {
$heading = $match.Groups[1].Value -replace '\[([^\]]*)\]\([^)]*\)', '$1' -replace '[`*]', ''
$anchor = [regex]::Replace($heading.Trim().ToLowerInvariant(), '[^\p{L}\p{Nd}\s_-]', '') -replace ' ', '-'
if ($seen.ContainsKey($anchor)) {
$seen[$anchor]++
'{0}-{1}' -f $anchor, $seen[$anchor]
} else {
$seen[$anchor] = 0
$anchor
}
}
}
}
Describe 'Export-WikiContent.ps1' {
Context 'When it converts a sample of Docs' {
BeforeAll {
$repositoryPath = Join-Path -Path $TestDrive -ChildPath 'repository'
$docsPath = Join-Path -Path $repositoryPath -ChildPath 'Docs'
$wikiPath = Join-Path -Path $TestDrive -ChildPath 'wiki'
foreach ($folder in "$docsPath\Cmdlets", "$docsPath\Contributing", "$wikiPath\.git") {
New-Item -ItemType Directory -Path $folder -Force | Out-Null
}
# A clone of the wiki with pages that Docs doesn't have
Set-Content -LiteralPath "$wikiPath\.git\config" -Value '[core]'
Set-Content -LiteralPath "$wikiPath\Version-History.textile" -Value '* 4.2.4'
Set-Content -LiteralPath "$wikiPath\Old-Page.md" -Value '# Old page'
Set-Content -LiteralPath "$repositoryPath\CHANGELOG.md" -Value '# Changelog'
Set-Content -LiteralPath "$docsPath\Contributing.md" -Value '# Contributing'
Set-Content -LiteralPath "$docsPath\Contributing\01-Getting-Started.md" -Value '# Get started'
Set-Content -LiteralPath "$docsPath\Concepts.md" -Value "# Concepts`n`n## Rights`n`nSee [Home](README.md)."
Set-Content -LiteralPath "$docsPath\README.md" -Value @'
# Thing
[Concepts](Concepts.md#rights), [Get-Thing](Cmdlets/Get-Thing.md), and [`Set-Thing`](Cmdlets/Set-Thing.md).
[Changelog](../CHANGELOG.md), [guide](Contributing.md), and [first steps](Contributing/01-Getting-Started.md).
[Example site](https://example.com/page.md) and [cmdlets](#cmdlets).
Code keeps its links: `[Concepts](Concepts.md)`.
```powershell
# [Concepts](Concepts.md)
```
## Installation
Install the module.
## Cmdlets
### Getting
| Cmdlet | Description |
| --- | --- |
| [Get-Thing](Cmdlets/Get-Thing.md) | Gets a thing. |
### Setting
| Cmdlet | Description |
| --- | --- |
| [Set-Thing](Cmdlets/Set-Thing.md) | Sets a thing. |
'@
$metadata = "---`nexternal help file: Thing.dll-Help.xml`nonline version: https://example.com`nschema: 2.0.0`n---`n`n"
Set-Content -LiteralPath "$docsPath\Cmdlets\Get-Thing.md" -Value ($metadata +
"# Get-Thing`n`nSee [rights](../Concepts.md#rights), [Set-Thing](Set-Thing.md), and [home](../README.md).")
Set-Content -LiteralPath "$docsPath\Cmdlets\Set-Thing.md" -Value ($metadata +
"# Set-Thing`n`nSee [Get-Thing](Get-Thing.md).")
& $exportScript -Path $docsPath -DestinationPath $wikiPath -RepositoryUrl 'https://github.com/contoso/Thing' -Branch 'main'
$homePage = Get-Content -LiteralPath "$wikiPath\Home.md" -Raw
$sidebar = Get-Content -LiteralPath "$wikiPath\_Sidebar.md" -Raw
}
It 'Should write Home, Concepts, the cmdlet pages, How-to-install, the sidebar, and the footer' {
$expected = 'Home.md', 'Concepts.md', 'Get-Thing.md', 'Set-Thing.md', 'How-to-install.md', '_Sidebar.md', '_Footer.md'
((Get-ChildItem -LiteralPath $wikiPath -File).Name | Sort-Object) -join ', ' |
Should -BeExactly (($expected | Sort-Object) -join ', ')
}
It 'Should keep the .git folder and remove the pages that Docs does not have' {
"$wikiPath\.git\config" | Should -Exist
"$wikiPath\Version-History.textile" | Should -Not -Exist
"$wikiPath\Old-Page.md" | Should -Not -Exist
}
It 'Should not publish the contributor guide' {
Get-ChildItem -LiteralPath $wikiPath -Recurse -File -Filter '*Get*Started*' | Should -BeNullOrEmpty
"$wikiPath\Contributing.md" | Should -Not -Exist
}
It 'Should convert <Link> on <Page> into <Expected>' -ForEach @(
@{ Page = 'Home'; Link = '[Concepts](Concepts.md#rights)'; Expected = '[Concepts](Concepts#rights)' }
@{ Page = 'Home'; Link = '[Get-Thing](Cmdlets/Get-Thing.md)'; Expected = '[Get-Thing](Get-Thing)' }
@{ Page = 'Home'; Link = '[`Set-Thing`](Cmdlets/Set-Thing.md)'; Expected = '[`Set-Thing`](Set-Thing)' }
@{ Page = 'Home'; Link = '[Changelog](../CHANGELOG.md)'; Expected = '[Changelog](https://github.com/contoso/Thing/blob/main/CHANGELOG.md)' }
@{ Page = 'Home'; Link = '[guide](Contributing.md)'; Expected = '[guide](https://github.com/contoso/Thing/blob/main/Docs/Contributing.md)' }
@{ Page = 'Home'; Link = '[first steps](Contributing/01-Getting-Started.md)'; Expected = '[first steps](https://github.com/contoso/Thing/blob/main/Docs/Contributing/01-Getting-Started.md)' }
@{ Page = 'Home'; Link = '[Example site](https://example.com/page.md)'; Expected = '[Example site](https://example.com/page.md)' }
@{ Page = 'Home'; Link = '[cmdlets](#cmdlets)'; Expected = '[cmdlets](#cmdlets)' }
@{ Page = 'Concepts'; Link = '[Home](README.md)'; Expected = '[Home](Home)' }
@{ Page = 'Get-Thing'; Link = '[rights](../Concepts.md#rights)'; Expected = '[rights](Concepts#rights)' }
@{ Page = 'Get-Thing'; Link = '[Set-Thing](Set-Thing.md)'; Expected = '[Set-Thing](Set-Thing)' }
@{ Page = 'Get-Thing'; Link = '[home](../README.md)'; Expected = '[home](Home)' }
) {
$content = Get-Content -LiteralPath "$wikiPath\$Page.md" -Raw
$content | Should -Match ([regex]::Escape($Expected))
if ($Link -ne $Expected) {
$content | Should -Not -Match ([regex]::Escape($Link))
}
}
It 'Should keep links in code unchanged' {
$homePage | Should -Match ([regex]::Escape('`[Concepts](Concepts.md)`'))
$homePage | Should -Match ([regex]::Escape('# [Concepts](Concepts.md)'))
}
It 'Should remove the platyPS metadata from the cmdlet pages' {
Get-Content -LiteralPath "$wikiPath\Get-Thing.md" -TotalCount 1 | Should -BeExactly '# Get-Thing'
}
It 'Should link Home and the other pages in the sidebar' {
$sidebar | Should -Match ([regex]::Escape('[Home](Home)'))
$sidebar | Should -Match ([regex]::Escape('[Concepts](Concepts)'))
}
It 'Should list the cmdlets in the sidebar under the groups of Docs/README.md' {
$sidebar | Should -Match '(?s)Getting.*\[Get-Thing\]\(Get-Thing\).*Setting.*\[Set-Thing\]\(Set-Thing\)'
}
It 'Should say in the footer that the wiki is generated from Docs' {
Get-Content -LiteralPath "$wikiPath\_Footer.md" -Raw |
Should -Match ([regex]::Escape('(https://github.com/contoso/Thing/tree/main/Docs)'))
}
It 'Should keep the address of the former page How-to-install' {
Get-Content -LiteralPath "$wikiPath\How-to-install.md" -Raw | Should -Match ([regex]::Escape('(Home#installation)'))
}
}
Context 'When it converts the documentation of the repository' {
BeforeAll {
$repositoryPath = Join-Path -Path $PSScriptRoot -ChildPath '..'
$docsPath = Join-Path -Path $repositoryPath -ChildPath 'Docs'
$wikiPath = Join-Path -Path $TestDrive -ChildPath 'wiki-of-the-repository'
& $exportScript -Path $docsPath -DestinationPath $wikiPath
$pages = Get-ChildItem -LiteralPath $wikiPath -Filter '*.md'
$cmdletNames = (Get-ChildItem -LiteralPath (Join-Path -Path $docsPath -ChildPath 'Cmdlets') -Filter '*.md').BaseName
}
It 'Should write a page for every page in Docs except the contributor guide, and one for every cmdlet' {
$topPages = (Get-ChildItem -LiteralPath $docsPath -Filter '*.md' |
Where-Object -Property Name -NotIn -Value 'README.md', 'Contributing.md').BaseName
$expected = @('Home', 'How-to-install', '_Sidebar', '_Footer') + $topPages + $cmdletNames
($pages.BaseName | Sort-Object) -join ', ' | Should -BeExactly (($expected | Sort-Object) -join ', ')
}
It 'Should keep the page name Version-History, which the release notes of 4.2.4 and 4.2.6 link to' {
Join-Path -Path $wikiPath -ChildPath 'Version-History.md' | Should -Exist
}
It 'Should list every cmdlet in the sidebar' {
$sidebar = Get-Content -LiteralPath (Join-Path -Path $wikiPath -ChildPath '_Sidebar.md') -Raw
$cmdletNames | Where-Object -FilterScript { $sidebar -notmatch ('\]\({0}\)' -f [regex]::Escape($_)) } |
Should -BeNullOrEmpty
}
It 'Should link only to existing wiki pages, their anchors, and existing files of the repository' {
$anchors = @{}
foreach ($page in $pages) {
$anchors[$page.BaseName] = @(Get-GitHubAnchor -Markdown (Get-Content -LiteralPath $page.FullName -Raw))
}
$brokenLinks = foreach ($page in $pages) {
$text = [regex]::Replace((Get-Content -LiteralPath $page.FullName -Raw), '(?ms)^```.*?^```', '')
$text = [regex]::Replace($text, '`[^`\n]+`', '')
foreach ($match in [regex]::Matches($text, '\]\((?<url>[^)\s]+)\)')) {
$url = $match.Groups['url'].Value
if ($url -match '^https://github\.com/raandree/NTFSSecurity/(?:blob|tree)/master/(?<path>[^#]+)') {
if (-not (Test-Path -LiteralPath (Join-Path -Path $repositoryPath -ChildPath $Matches['path']))) {
'{0}: {1}' -f $page.BaseName, $url
}
} elseif ($url -notmatch '^[a-z]+:') {
$target, $anchor = $url -split '#', 2
if (-not $target) {
$target = $page.BaseName
}
if (-not $anchors.ContainsKey($target) -or ($anchor -and $anchors[$target] -notcontains $anchor)) {
'{0}: {1}' -f $page.BaseName, $url
}
}
}
}
$brokenLinks | Should -BeNullOrEmpty
}
}
}

86
appveyor.yml

@ -1,86 +0,0 @@
# 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, and runs the Pester tests against the build.
image: Visual Studio 2022
init:
- ps: git config --global core.autocrlf true
install:
- ps: |
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
Install-Module -Name platyPS -RequiredVersion 0.14.2 -Force
Install-Module -Name MarkdownLinkCheck -RequiredVersion 0.2.0 -Force
# The image includes Pester 3.4.0, which is signed by a different publisher.
Install-Module -Name Pester -RequiredVersion 5.7.1 -Force -SkipPublisherCheck
before_build:
- nuget restore NTFSSecurity\packages.config -PackagesDirectory packages -NonInteractive
- nuget restore Security2\packages.config -PackagesDirectory packages -NonInteractive
# Provides the .NET Framework 4.5.2 reference assemblies, so the build does
# not depend on a targeting pack installed on the build image.
- nuget install Microsoft.NETFramework.ReferenceAssemblies.net452 -Version 1.0.3 -OutputDirectory packages -NonInteractive
build_script:
- ps: |
$referenceAssemblies = "$env:APPVEYOR_BUILD_FOLDER\packages\Microsoft.NETFramework.ReferenceAssemblies.net452.1.0.3\build"
msbuild NTFSSecurity\NTFSSecurity.csproj /nologo /verbosity:minimal /p:Configuration=Release "/p:TargetFrameworkRootPath=$referenceAssemblies" "/p:FrameworkPathOverride=$referenceAssemblies\.NETFramework\v4.5.2"
if ($LASTEXITCODE -ne 0) {
throw "MSBuild failed with exit code $LASTEXITCODE."
}
test_script:
- ps: |
$ErrorActionPreference = 'Stop'
Import-Module -Name platyPS
Import-Module -Name MarkdownLinkCheck
Import-Module -Name .\NTFSSecurity\bin\Release\NTFSSecurity.psd1 -Force
# 01. Test that the documentation matches the cmdlets built from source
Update-MarkdownHelp -Path ./Docs/Cmdlets | Out-Null
$diff = git diff -- Docs/Cmdlets
if ($diff) {
throw "Help is not up-to-date, run Update-MarkdownHelp: $diff"
}
# 02. Verify hyperlinks
$brokenLinks = Get-MarkdownLink -Path .\Docs\ -BrokenOnly
if ($brokenLinks) {
throw "Found broken hyperlinks $brokenLinks"
}
# 03. Test that the help file of the module matches the documentation
New-ExternalHelp -Path ./Docs/Cmdlets -OutputPath ./NTFSSecurity/en-US -Force | Out-Null
$helpChanges = git status --porcelain -- NTFSSecurity/en-US
if ($helpChanges) {
throw "The help file is not up-to-date, run New-ExternalHelp -Path .\Docs\Cmdlets -OutputPath .\NTFSSecurity\en-US -Force: $helpChanges"
}
# 04. Run the Pester tests against the module build
Import-Module -Name Pester -RequiredVersion 5.7.1
$configuration = New-PesterConfiguration
$configuration.Run.Path = '.\Tests'
$configuration.Run.PassThru = $true
$configuration.Output.Verbosity = 'Detailed'
$result = Invoke-Pester -Configuration $configuration
# Report every test once on the Tests tab. An uploaded NUnit file lists a
# Pester 5 test once for each block that contains it.
if ($env:APPVEYOR_API_URL -and $result.Tests) {
$tests = @(foreach ($test in $result.Tests) {
@{
testName = $test.ExpandedPath
testFramework = 'Pester'
fileName = Split-Path -Path $test.ScriptBlock.File -Leaf
outcome = if ($test.Result -eq 'NotRun') { 'NotRunnable' } else { "$($test.Result)" }
durationMilliseconds = [long] $test.Duration.TotalMilliseconds
ErrorMessage = @($test.ErrorRecord | ForEach-Object -Process { "$_" }) -join [Environment]::NewLine
}
})
$body = [Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject $tests -Compress))
Invoke-RestMethod -Method Post -Uri ($env:APPVEYOR_API_URL.TrimEnd('/') + '/api/tests/batch') -Body $body -ContentType 'application/json; charset=utf-8' | Out-Null
}
if ($result.FailedCount -gt 0) {
throw "$($result.FailedCount) Pester tests failed."
}
Loading…
Cancel
Save