# 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 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: pull_request: push: branches: - master tags: - '[0-9]+.[0-9]+.[0-9]+' - '[0-9]+.[0-9]+.[0-9]+-*' 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 # 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 # Shows the pages that would change. Read-only: on pull requests, including # those of Dependabot, this job runs code that nobody has reviewed yet. permissions: contents: read 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 publish-wiki: name: Publish the wiki needs: wiki if: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') runs-on: ubuntu-latest timeout-minutes: 10 # This job can write, so it runs only for master, after the changes were # reviewed and merged. 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 Add-Content -LiteralPath $env:GITHUB_ENV -Value "WIKI_PATH=$wiki" - name: Publish the wiki 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." } 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." }