Browse Source

ci: move CI from AppVeyor to GitHub Actions

.github/workflows/ci.yml replaces appveyor.yml and runs on pull requests
and pushes to master:

- build (windows-2025): the same restore, Release build, and documentation
  checks as before, then the Pester tests in Windows PowerShell 5.1 and in
  PowerShell 7. .github/scripts/Invoke-Tests.ps1 writes the counts and
  the failed tests to the job summary and the NUnit file to the
  test-results artifact, and also fails on test files that fail.
- wiki (ubuntu-latest): generates the wiki from Docs; on pull requests it
  lists the pages that would change, from master it publishes them with
  the built-in token. Only this job has contents: write.

Actions are pinned by commit SHA. Every native command checks its exit
code, because GitHub checks only the last one. The contributor guide
describes the workflow and the wiki.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/96/head
Raimund Andree 1 week ago
parent
commit
8695d8d63b
  1. 79
      .github/scripts/Invoke-Tests.ps1
  2. 181
      .github/workflows/ci.yml
  3. 48
      Docs/Contributing/02-Writing.md
  4. 86
      appveyor.yml

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."
}

48
Docs/Contributing/02-Writing.md

@ -1,8 +1,9 @@
# Write documentation
The NTFSSecurity documentation is written in Markdown and lives in the
`Docs` folder of the repository, where GitHub renders it. This page explains
how the documentation is organized and how to change it.
`Docs` folder of the repository, where GitHub renders it. GitHub Actions also
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
@ -17,6 +18,8 @@ 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/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,
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
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
`appveyor.yml` show the commands that the CI build uses:
`NTFSSecurity\bin\Release`; the steps "Restore the NuGet packages" and "Build
the module" in `.github/workflows/ci.yml` show the commands that the CI build
uses:
```powershell
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
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
Before you open a pull request, check the following:
- No page in `Docs/Cmdlets` contains a `{{ ... }}` placeholder.
- `Update-MarkdownHelp` doesn't change any page in `Docs/Cmdlets`. The build
defined in `appveyor.yml` runs the same check.
- `Update-MarkdownHelp` doesn't change any page in `Docs/Cmdlets`. The CI
workflow runs the same check.
- `New-ExternalHelp` doesn't change
`NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml`. The build runs the same
check.
`NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml`. The CI workflow runs the
same check.
- The Pester tests in `Tests` pass. They test the module in
`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:
`NTFSSecurity\bin\Release`, for example that `Get-Help` shows every page,
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
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
```
- All links work. The build checks them with `Get-MarkdownLink` from the
MarkdownLinkCheck module:
- All links work. The CI workflow checks them with `Get-MarkdownLink` from
the MarkdownLinkCheck module:
```powershell
Get-MarkdownLink -Path .\Docs -BrokenOnly

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