├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitattributes ├── .github └── workflows │ ├── publish.yaml │ └── test.yml ├── .gitignore ├── .markdownlint.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Build └── Convert-PSAke.ps1 ├── CHANGELOG.md ├── LICENSE ├── PowerShellBuild ├── IB.tasks.ps1 ├── PowerShellBuild.psd1 ├── PowerShellBuild.psm1 ├── Private │ └── Remove-ExcludedItem.ps1 ├── Public │ ├── Build-PSBuildMAMLHelp.ps1 │ ├── Build-PSBuildMarkdown.ps1 │ ├── Build-PSBuildModule.ps1 │ ├── Build-PSBuildUpdatableHelp.ps1 │ ├── Clear-PSBuildOutputFolder.ps1 │ ├── Initialize-PSBuild.ps1 │ ├── Publish-PSBuildModule.ps1 │ ├── Test-PSBuildPester.ps1 │ └── Test-PSBuildScriptAnalysis.ps1 ├── ScriptAnalyzerSettings.psd1 ├── build.properties.ps1 └── psakeFile.ps1 ├── README.md ├── build.ps1 ├── build.settings.ps1 ├── cspell.json ├── media ├── ib_example.png ├── psake_example.png └── psaketaskmodule-256x256.png ├── psakeFile.ps1 ├── requirements.psd1 └── tests ├── Help.tests.ps1 ├── IBTasks.tests.ps1 ├── Manifest.tests.ps1 ├── Meta.tests.ps1 ├── MetaFixers.psm1 ├── ScriptAnalyzerSettings.psd1 ├── TestModule ├── .build.ps1 ├── .gitattributes ├── .github │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── settings.json │ └── tasks.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── TestModule │ ├── Private │ │ ├── GetHelloWorld.ps1 │ │ └── excludemealso.ps1 │ ├── Public │ │ └── Get-HelloWorld.ps1 │ ├── TestModule.psd1 │ ├── TestModule.psm1 │ ├── dontcopy │ │ └── garbage.txt │ ├── excludeme.txt │ └── stuff │ │ └── copymealways.txt ├── Tests │ ├── Help.tests.ps1 │ ├── Manifest.tests.ps1 │ ├── Meta.tests.ps1 │ ├── MetaFixers.psm1 │ ├── ScriptAnalyzerSettings.psd1 │ └── a_InModuleScope.tests.ps1 ├── azure-pipelines.yml ├── build.ps1 ├── mkdocs.yml ├── psakeFile.ps1 └── requirements.psd1 └── build.tests.ps1 /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | FROM mcr.microsoft.com/powershell:latest 7 | 8 | # This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" 9 | # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs 10 | # will be updated to match your local UID/GID (when using the dockerFile property). 11 | # See https://aka.ms/vscode-remote/containers/non-root-user for details. 12 | ARG USERNAME=vscode 13 | ARG USER_UID=1000 14 | ARG USER_GID=$USER_UID 15 | 16 | # install git iproute2, process tools 17 | RUN apt-get update && apt-get -y install git openssh-client less iproute2 procps \ 18 | # Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user. 19 | && groupadd --gid $USER_GID $USERNAME \ 20 | && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ 21 | # [Optional] Add sudo support for the non-root user 22 | && apt-get install -y sudo \ 23 | && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ 24 | && chmod 0440 /etc/sudoers.d/$USERNAME \ 25 | # 26 | # Clean up 27 | && apt-get autoremove -y \ 28 | && apt-get clean -y \ 29 | && rm -rf /var/lib/apt/lists/* 30 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PowerShell", 3 | "dockerFile": "Dockerfile", 4 | // "image": "mcr.microsoft.com/powershell", 5 | 6 | // Set *default* container specific settings.json values on container create. 7 | "settings": { 8 | "terminal.integrated.shell.linux": "/usr/bin/pwsh" 9 | }, 10 | 11 | // Add the IDs of extensions you want installed when the container is created. 12 | "extensions": [ 13 | "ms-vscode.powershell", 14 | "davidanson.vscode-markdownlint" 15 | ], 16 | 17 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 18 | // "forwardPorts": [], 19 | 20 | // Bootstrap build modules 21 | "postCreateCommand": "pwsh -c './build.ps1 -Task Init -Bootstrap'", 22 | 23 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. 24 | // "remoteUser": "vscode" 25 | } 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | workflow_dispatch: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | name: Publish 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Publish 14 | shell: pwsh 15 | run: | 16 | $apiKey = '${{ secrets.PSGALLERY_API_KEY }}' | ConvertTo-SecureString -AsPlainText -Force 17 | $cred = [pscredential]::new('apikey', $apiKey) 18 | ./build.ps1 -Task Publish -PSGalleryApiKey $cred -Bootstrap 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: [ $default-branch ] 5 | pull_request: 6 | workflow_dispatch: 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest, windows-latest, macOS-latest] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Test 18 | shell: pwsh 19 | run: ./build.ps1 -Task Test -Bootstrap 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Output/ 2 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DavidAnson/vscode-markdownlint/refs/heads/main/markdownlint-config-schema.json", 3 | "MD024": { 4 | "siblings_only": true 5 | }, 6 | "MD013": { 7 | "code_blocks": false, 8 | "tables": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "PowerShell: Interactive Session", 9 | "type": "PowerShell", 10 | "request": "launch", 11 | "cwd": "" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": true, 3 | "files.trimTrailingWhitespace": true, 4 | "files.insertFinalNewline": true, 5 | "powershell.codeFormatting.preset": "OTBS", 6 | "powershell.codeFormatting.addWhitespaceAroundPipe": true, 7 | "powershell.codeFormatting.useCorrectCasing": true, 8 | "powershell.codeFormatting.newLineAfterOpenBrace": true, 9 | "powershell.codeFormatting.alignPropertyValuePairs": true 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | 6 | // Start PowerShell (pwsh on *nix) 7 | "windows": { 8 | "options": { 9 | "shell": { 10 | "executable": "powershell.exe", 11 | "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command" ] 12 | } 13 | } 14 | }, 15 | "linux": { 16 | "options": { 17 | "shell": { 18 | "executable": "/usr/bin/pwsh", 19 | "args": [ "-NoProfile", "-Command" ] 20 | } 21 | } 22 | }, 23 | "osx": { 24 | "options": { 25 | "shell": { 26 | "executable": "/usr/local/bin/pwsh", 27 | "args": [ "-NoProfile", "-Command" ] 28 | } 29 | } 30 | }, 31 | 32 | "tasks": [ 33 | { 34 | "label": "Clean", 35 | "type": "shell", 36 | "command": "${cwd}/build.ps1 -Task Clean -Verbose" 37 | }, 38 | { 39 | "label": "Test", 40 | "type": "shell", 41 | "command": "${cwd}/build.ps1 -Task Test -Verbose", 42 | "group": { 43 | "kind": "test", 44 | "isDefault": true 45 | }, 46 | "problemMatcher": "$pester" 47 | }, 48 | { 49 | "label": "Analyze", 50 | "type": "shell", 51 | "command": "${cwd}/build.ps1 -Task Analyze -Verbose" 52 | }, 53 | { 54 | "label": "Pester", 55 | "type": "shell", 56 | "command": "${cwd}/build.ps1 -Task Pester -Verbose", 57 | "problemMatcher": "$pester" 58 | }, 59 | { 60 | "label": "Build", 61 | "type": "shell", 62 | "command": "${cwd}/build.ps1 -Task Build -Verbose", 63 | "group": { 64 | "kind": "build", 65 | "isDefault": true 66 | } 67 | }, 68 | { 69 | "label": "Publish", 70 | "type": "shell", 71 | "command": "${cwd}/build.ps1 -Task Publish -Verbose" 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /Build/Convert-PSAke.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5 -module psake 2 | using namespace System.Management.Automation 3 | 4 | <# 5 | .SYNOPSIS 6 | Converts a PSAke script to an invoke-build script, using best practices where possible 7 | .NOTES 8 | Some Params are private in case the variables are used in the psake script 9 | #> 10 | [CmdletBinding()] 11 | param( 12 | #The path to the PSAke file 13 | [Parameter(Mandatory)][string]$PRIVATE:Source, 14 | #The default tab width, in number of spaces. TODO: Auto-Discover this 15 | [int]$ConvertPSAkeTabWidth = 4 16 | ) 17 | 18 | #region Initialize 19 | $PSCommonParameters = ([System.Management.Automation.PSCmdlet]::CommonParameters + [System.Management.Automation.PSCmdlet]::OptionalCommonParameters) 20 | $ConvertPSAkeIndent = " " * $ConvertPSAkeTabWidth 21 | #endregion Initalize 22 | 23 | #region psakeDiscovery 24 | $psakeModule = import-module psake -PassThru 25 | $psakeStatements = $psakeModule.ExportedCommands.keys.where{$PSItem -notmatch '-'} 26 | #endregion psakeDiscovery 27 | 28 | #region StatementConversions 29 | function Convert-PsakeStatement ([Language.StatementAst]$statement) { 30 | $statementText = $statement.extent.text 31 | $psakeStatement = ($statementText | Select-String '^\w+\b').matches.value 32 | 33 | #Escape out variables in parameters so they get passed through literally 34 | #TODO: Do this with AST instead of RegEX 35 | $statementText = $statementText -replace '(\$[\.\w]*)',(@("'",'$1',"'") -join $null) 36 | 37 | if ($psakeStatement -in $psakeStatements) { 38 | if (-not (Get-Alias "$psakeStatement" -erroraction SilentlyContinue)) { 39 | if (get-command "Convert-$psakeStatement" -erroraction SilentlyContinue) { 40 | Set-Alias "$psakeStatement" "Convert-$psakeStatement" 41 | } else { 42 | "<# CONVERTWARNING: This script does not support the $psakeStatement statement yet" 43 | $statementText 44 | '#>' 45 | continue 46 | } 47 | } 48 | $result = Invoke-Command -scriptblock ([ScriptBlock]::Create($statementText)) 49 | $result -join [Environment]::Newline 50 | } else { 51 | '<# CONVERTWARNING: Did not recognize this as a psake-allowed statement' 52 | $statementText 53 | '#>' 54 | continue 55 | } 56 | } 57 | 58 | function Convert-FormatTaskName([scriptblock]$formatScriptBlock) { 59 | write-output '<# CONVERT-TODO: Custom task headers, see the repository Tasks/Header for details.' 60 | write-output $formatScriptBlock 61 | write-output '#>' 62 | } 63 | 64 | function Convert-Properties([scriptblock]$properties) { 65 | #Parse into statements and separate variables from other statement types 66 | $parsedStatementResult = $properties.ast.endblock.statements.where({$PSItem.gettype().Name -match 'AssignmentStatementAst'},'Split') 67 | $psakeParams = $parsedStatementResult[0] 68 | $otherStatements = $parsedStatementResult[1] 69 | if ($psakeParams) { 70 | #TODO: Fix Indent 71 | 'param (' 72 | $psakeParams.extent.text -join (',' + [Environment]::NewLine) 73 | ')' 74 | } 75 | if ($otherStatements) { 76 | 'Enter-Build {' 77 | $otherStatements.extent.text 78 | '}' 79 | } 80 | } 81 | 82 | function Convert-Framework([string]$framework) { 83 | '# CONVERT-TODO: Specify used tool names exactly as they are used in the script.' 84 | '# MSBuild is an example. It should be used as MSBuild, not MSBuild.exe' 85 | '# Example with more tools: use 4.0 MSBuild, csc, ngen' 86 | if ($framework -notmatch '^\d+\.\d+$') { 87 | "# CONVERT-TODO: The form '$framework' is not supported. See help:" 88 | '# . Invoke-Build; help -full Use-BuildAlias' 89 | } 90 | "use $framework MSBuild" 91 | } 92 | 93 | function Convert-Include([string]$fileNamePathToInclude) { 94 | '# CONVERT-TODO: Decide whether it is dot-sourced (.) or just invoked (&).' 95 | ". '$($fileNamePathToInclude.Replace("'", "''"))'" 96 | } 97 | 98 | function Convert-TaskSetup([scriptblock]$setup) { 99 | "Enter-BuildTask {$setup}" 100 | } 101 | 102 | function Convert-TaskTearDown([scriptblock]$teardown) { 103 | "Exit-BuildTask {$teardown}" 104 | } 105 | 106 | function Convert-Task 107 | { 108 | param( 109 | [string]$name, 110 | [scriptblock]$action, 111 | [scriptblock]$preaction, 112 | [scriptblock]$postaction, 113 | [scriptblock]$precondition, 114 | [scriptblock]$postcondition, 115 | [switch]$continueOnError, 116 | [string[]]$depends, 117 | [string[]]$requiredVariables, 118 | [string]$description, 119 | [string]$alias 120 | ) 121 | 122 | if ($name -eq '?') { 123 | "# CONVERTWARNING: You specified a '?' task. Invoke-Build has its own built-in function for this so it was not copied." 124 | continue 125 | } 126 | 127 | if ($description) { 128 | $description = $description -replace '[\r\n]+', ' ' 129 | "# Synopsis: $description" 130 | } 131 | 132 | if ($alias) {"# CONVERT-TODO: Alias '$alias' is not supported. Do not use it or define another task: task $alias $name"} 133 | if ($continueOnError) {"# CONVERT-TODO: ContinueOnError is not supported. Instead, callers use its safe reference as '?$name'"} 134 | 135 | $newTaskHeader = 'task ' 136 | if ($name -eq 'default') { 137 | '# Default task converted from PSAke. If it is the first then any name can be used instead.' 138 | $newTaskHeader += '.' 139 | } 140 | else { 141 | $newTaskHeader += $name 142 | } 143 | if ($precondition) { 144 | $newTaskHeader += " -If {$precondition}" 145 | } 146 | 147 | $newTaskJobs = @() 148 | if ($depends) { 149 | $newTaskJobs += $depends 150 | } 151 | if ($preaction) { 152 | $newTaskJobs += "{$preaction}" 153 | } 154 | if ($action) { 155 | if ($requiredVariables) { 156 | $outAction = New-Object Text.StringBuilder 157 | $outAction.AppendLine() > $null 158 | $requiredVariables.foreach{ 159 | $outAction.appendLine("${ConvertPSAkeIndent}property $PSItem") > $null 160 | } 161 | $outaction.appendLine($action.tostring()) > $null 162 | $action = [Scriptblock]::Create($outaction.tostring()) 163 | } 164 | $newTaskJobs += "{$action}" 165 | } 166 | if ($postaction) { 167 | $newTaskJobs += "{$postaction}" 168 | } 169 | if ($postcondition) { 170 | $newTaskJobs += " { assert `$($postcondition) }" 171 | } 172 | 173 | #output the formatted header 174 | $newTaskHeader + " " + ($newTaskJobs -join ', ') 175 | } 176 | #endregion StatementConversions 177 | 178 | 179 | #region Main 180 | New-Variable parseTokens -force 181 | New-Variable parseErrors -force 182 | 183 | $ErrorActionPreference = 'Stop' 184 | 185 | $sourceRaw = (Get-Content -Path $Source -Raw) 186 | 187 | #Psake Variable Substitution 188 | #TODO: More structured method using AST 189 | #TODO: Variable Collision Detection 190 | $sourceRaw = $sourceRaw -replace [regex]::Escape('$psake.context.currentTaskName'), '$task.name' 191 | $sourceRaw = $sourceRaw -replace [regex]::Escape('$psake.context.Peek().Tasks.Keys'), '$BuildTask' 192 | $sourceRaw = $sourceRaw -replace [regex]::Escape('$psake.version'), '[string](get-command invoke-build | % version)' 193 | $sourceRaw = $sourceRaw -replace [regex]::Escape('$psake.build_script_file'), '$BuildFile' 194 | $sourceRaw = $sourceRaw -replace [regex]::Escape('$psake.build_script_dir'), '(Split-Path $BuildFile -Parent)' 195 | 196 | 197 | $out = New-Object System.Text.Stringbuilder 198 | 199 | $parseResult = [Language.Parser]::ParseInput( 200 | $sourceRaw, #Source of data 201 | [ref]$parseTokens, #Tokens found during parsing 202 | [ref]$parseErrors #Any parse errors found 203 | ) 204 | 205 | if ($parseErrors) {throw "Parsing Errors Found! Please fix first: $parseErrors"} 206 | 207 | #Parse the Powershell source into statement blocks 208 | $statements = $ParseResult.endblock.statements 209 | 210 | $statements.foreach{ 211 | $statementItem = $PSItem 212 | $statementItemText = $statementItem.extent.text 213 | 214 | #TODO: AST-based variable substitution for $psake 215 | $unsupportedPsakeVarErrors = [ordered]@{ 216 | 'context' = '$psake.context is unsupported. Use properties instead' 217 | 'config_default' = '$psake.config_default is unsupported. Use Invoke-Build -Result instead' 218 | 'run_by_psake_build_tester' = '$psake.run_by_psake_build_tester is unsupported. Use Invoke-Build -Result instead' 219 | 'build_success'= '$psake.build_success is unsupported. Use Invoke-Build -Result instead' 220 | 'error_message' = '$psake.error_message is unsupported. Use Invoke-Build -Result instead' 221 | '' = 'Use of the $psake variable is unsupported. Use Invoke-Build -Result to get build status' 222 | } 223 | $psakeVarFound = $false 224 | foreach ($psakeVarErrorItem in $unsupportedPsakeVarErrors.keys) { 225 | $unsupportedPsakeVarsRegex = "\`$psake\.?$([regex]::Escape($psakeVarErrorItem))" 226 | if ($statementItemText -match $unsupportedPsakeVarsRegex) { 227 | $out.AppendLine("<# CONVERTWARNING: " + $unsupportedPsakeVarErrors[$psakeVarErrorItem]) > $null 228 | $out.AppendLine($statementItemText) > $null 229 | $out.AppendLine('#>') > $null 230 | $psakeVarFound = $true 231 | break 232 | } 233 | } 234 | if ($psakeVarFound) {return} 235 | 236 | switch ($statementItem.gettype().name) { 237 | 'PipelineAst' { 238 | switch -regex ($statementItem.extent.text) { 239 | #Known psake functions 240 | "^($($psakeStatements -join '|'))\b" { 241 | try { 242 | $convertStatementResult = Convert-PsakeStatement $StatementItem 243 | if ($convertStatementResult -is [String]) { 244 | $out.AppendLine($convertStatementResult) > $null 245 | } 246 | } 247 | catch { 248 | $out.AppendLine("<# CONVERTWARNING: This statement was copied not converted due to the error: $PSItem") > $null 249 | $out.AppendLine($statementItemText) > $null 250 | $out.AppendLine('#>') > $null 251 | } 252 | } 253 | default { 254 | $out.AppendLine('#CONVERTWARNING: psake to InvokeBuild Conversion did not recognize this codeblock and passed through as-is. Consider moving it to Enter-Build') > $null 255 | $out.AppendLine($statementItemText) > $null 256 | } 257 | } 258 | } 259 | 'AssignmentStatementAst' { 260 | $out.AppendLine($statementItemText) > $null 261 | } 262 | default { 263 | $out.AppendLine('#CONVERT-WARNING: psake to InvokeBuild Conversion did not recognize this codeblock and passed through as-is') > $null 264 | $out.AppendLine($statementItemText) > $null 265 | } 266 | } 267 | 268 | #Add a line break between statements for consistency 269 | $out.AppendLine() > $null 270 | } 271 | 272 | $out.tostring() 273 | #endregion Main 274 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [0.7.2] unreleased 9 | 10 | ### Added 11 | 12 | - The `$PSBPreference` variable now supports the following PlatyPS `New-MarkdownHelp` and `Update-MarkdownHelp` boolean 13 | options: 14 | - `$PSBPreference.Docs.AlphabeticParamsOrder` 15 | - `$PSBPreference.Docs.ExcludeDontShow` 16 | - `$PSBPreference.Docs.UseFullTypeName` 17 | - The `$PSBPreference` variable now supports the following Pester test 18 | configuration options: 19 | - `$PSBPreference.Test.SkipRemainingOnFailure` can be set to **None**, 20 | **Run**, **Container** and **Block**. The default value is **None**. 21 | - `$PSBPreference.Test.OutputVerbosity` can be set to **None**, **Normal**, 22 | **Detailed**, and **Diagnostic**. The default value is **Detailed**. 23 | 24 | ## [0.7.1] 2025-04-01 25 | 26 | ### Fixes 27 | 28 | - Fix a bug in `Build-PSBuildMarkdown` where a hashtable item was added twice. 29 | 30 | ## [0.7.0] 2025-03-31 31 | 32 | ### Changed 33 | 34 | - [**#71**](https://github.com/psake/PowerShellBuild/pull/71) Compiled modules 35 | are now explicitly created as UTF-8 files. 36 | - [**#67**](https://github.com/psake/PowerShellBuild/pull/67) You can now 37 | overwrite existing markdown files using `$PSBPreference.Docs.Overwrite` and 38 | setting it to `$true`. 39 | - [**#72**](https://github.com/psake/PowerShellBuild/pull/72) Loosen 40 | dependencies by allowing them to be overwritten with 41 | `$PSBPreference.TaskDependencies`. 42 | 43 | ## [0.6.2] 2024-10-06 44 | 45 | ### Changed 46 | 47 | - Bump Pester to latest 5.6.1 48 | 49 | ### Fixed 50 | 51 | - [**#52**](https://github.com/psake/PowerShellBuild/pull/52) Pester object 52 | wasn't being passed back after running tests, causing the Pester task to never 53 | fail (via [@webtroter](https://github.com/webtroter)) 54 | - [**#55**](https://github.com/psake/PowerShellBuild/pull/55) Add `-Module` 55 | parameter to `Build-PSBuildUpdatableHelp` (via 56 | [@IMJLA](https://github.com/IMJLA)) 57 | - [**#60**](https://github.com/psake/PowerShellBuild/pull/60) Fix Windows 58 | PowerShell compatibility in `Initialize-PSBuild` (via 59 | [@joshooaj](https://github.com/joshooaj)) 60 | - [**#62**](https://github.com/psake/PowerShellBuild/pull/62) Fix code coverage 61 | output fle format not working (via 62 | [@OpsM0nkey](https://github.com/OpsM0nkey)) 63 | 64 | ## [0.6.1] 2021-03-14 65 | 66 | ### Fixed 67 | 68 | - Fixed bug in IB task `GenerateMarkdown` when dot sourcing precondition 69 | 70 | ## [0.6.0] 2021-03-14 71 | 72 | ### Changed 73 | 74 | - [**#50**](https://github.com/psake/PowerShellBuild/pull/50) Invoke-Build tasks 75 | brought inline with psake equivalents (via 76 | [@JustinGrote](https://github.com/JustinGrote)) 77 | 78 | ## [0.5.0] 2021-02-27 79 | 80 | ### Added 81 | 82 | - New code coverage parameters for setting output path and format: 83 | - `$PSBPreference.Test.CodeCoverage.OutputFile` - Output file path for code 84 | coverage results 85 | - `$PSBPreference.Test.CodeCoverage.OutputFileFormat` - Code coverage output 86 | format 87 | 88 | ## [0.5.0] (beta1) - 2020-11-15 89 | 90 | ### Added 91 | 92 | - When "compiling" a monolithic PSM1, add support for both inserting 93 | headers/footers for the entire PSM1, and for each script file. Control these 94 | via the following new build parameters (via 95 | [@pauby](https://github.com/pauby)) 96 | - `$PSBPreference.Build.CompileHeader` 97 | - `$PSBPreference.Build.CompileFooter` 98 | - `$PSBPreference.Build.CompileScriptHeader` 99 | - `$PSBPreference.Build.CompileScriptFooter` 100 | 101 | - Add ability to import project module from output directory prior to executing 102 | Pester tests. Toggle this with `$PSBPreference.Test.ImportModule`. Defaults to 103 | `$false`. (via [@joeypiccola](https://github.com/joeypiccola)) 104 | 105 | - Use `$PSBPreference.Build.CompileDirectories` to control directories who's 106 | contents will be concatenated into the PSM1 when 107 | `$PSBPreference.Build.CompileModule` is `$true`. Defaults to 108 | `@('Enum', 'Classes', 'Private', 'Public')`. 109 | - Use `$PSBPreference.Build.CopyDirectories` to control directories that will be 110 | copied "as is" into the built module. Default is an empty array. 111 | 112 | ### Changed 113 | 114 | - `$PSBPreference.Build.Exclude` now should be a list of regex expressions when 115 | `$PSBPreference.Build.CompileModule` is `$false` (default). 116 | 117 | - Use Pester v5 118 | 119 | ### Fixed 120 | 121 | - Overriding `$PSBPreference.Build.OutDir` now correctly determines the final 122 | module output directory. `$PSBPreference.Build.ModuleOutDir` is now computed 123 | internally and **SHOULD NOT BE SET DIRECTLY**. `$PSBPreference.Build.OutDir` 124 | will accept both relative and fully-qualified paths. 125 | 126 | - Before, when `$PSBPreference.Build.CompileModule` was set to `$true`, any 127 | files listed in `$PSBPreference.Build.Exclude` weren't being excluded like 128 | they should have been. Now, when it is `$true`, files matching regex 129 | expressions in `$PSBPreference.Build.Exclude` will be properly excluded (via 130 | [@pauby](https://github.com/pauby)) 131 | 132 | - `$PSBPreference.Help.DefaultLocale` now defaults to `en-US` on Linux since it 133 | is not correctly determined with `Get-UICulture`. 134 | 135 | ## [0.4.0] - 2019-08-31 136 | 137 | ### Changed 138 | 139 | - Allow using both `Credential` and `ApiKey` when publishing a module (via 140 | [@pauby](https://github.com/pauby)) 141 | 142 | ### Fixed 143 | 144 | - Don't overwrite Pester parameters when specifying `OutputPath` or 145 | `OutputFormat` (via [@ChrisLGardner](https://github.com/ChrisLGardner)) 146 | 147 | ## [0.3.1] - 2019-06-09 148 | 149 | ### Fixed 150 | 151 | - Don't create module page MD file. 152 | 153 | ## [0.3.0] - 2019-04-23 154 | 155 | ### Fixed 156 | 157 | - [**#24**](https://github.com/psake/PowerShellBuild/pull/24) Fix case of 158 | 'Public' folder when dot sourcing functions in PSM1 (via 159 | [@pauby](https://github.com/pauby)) 160 | 161 | ### Breaking changes 162 | 163 | - Refactor build properties into a single hashtable `$PSBPreference` 164 | 165 | ### Changed 166 | 167 | - [**#11**](https://github.com/psake/PowerShellBuild/pull/11) The Invoke-Build 168 | tasks are now auto-generated from the psake tasks via a converter script (via 169 | [@JustinGrote](https://github.com/JustinGrote)) 170 | 171 | - [**#19**](https://github.com/psake/PowerShellBuild/pull/19) Allow the 172 | `BHBuildOutput` environment variable defined by `BuildHelpers` to be set via 173 | the `$PSBPreference.Build.ModuleOutDir` property of the build tasks (via 174 | [@pauby](https://github.com/pauby)) 175 | 176 | ## [0.2.0] - 2018-11-15 177 | 178 | ### Added 179 | 180 | - Add `Publish` task to publish the module to the defined PowerShell Repository 181 | (PSGallery by default). 182 | 183 | ## [0.1.1] - 2018-11-09 184 | 185 | ### Fixed 186 | 187 | - [**#4**](https://github.com/psake/PowerShellBuild/pull/4) Fix syntax for 188 | `Analyze` task in `IB.tasks.ps1` (via 189 | [@nightroman](https://github.com/nightroman)) 190 | 191 | ## [0.1.0] - 2018-11-07 192 | 193 | ### Added 194 | 195 | - Initial commit 196 | 197 | 198 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brandon Olin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /PowerShellBuild/IB.tasks.ps1: -------------------------------------------------------------------------------- 1 | Remove-Variable -Name PSBPreference -Scope Script -Force -ErrorAction Ignore 2 | Set-Variable -Name PSBPreference -Option ReadOnly -Scope Script -Value (. ([IO.Path]::Combine($PSScriptRoot, 'build.properties.ps1'))) 3 | $__DefaultBuildDependencies = $PSBPreference.Build.Dependencies 4 | 5 | # Synopsis: Initialize build environment variables 6 | task Init { 7 | Initialize-PSBuild -UseBuildHelpers -BuildEnvironment $PSBPreference 8 | } 9 | 10 | # Synopsis: Clears module output directory 11 | task Clean Init, { 12 | Clear-PSBuildOutputFolder -Path $PSBPreference.Build.ModuleOutDir 13 | } 14 | 15 | # Synopsis: Builds module based on source directory 16 | task StageFiles Clean, { 17 | $buildParams = @{ 18 | Path = $PSBPreference.General.SrcRootDir 19 | ModuleName = $PSBPreference.General.ModuleName 20 | DestinationPath = $PSBPreference.Build.ModuleOutDir 21 | Exclude = $PSBPreference.Build.Exclude 22 | Compile = $PSBPreference.Build.CompileModule 23 | CompileDirectories = $PSBPreference.Build.CompileDirectories 24 | CopyDirectories = $PSBPreference.Build.CopyDirectories 25 | Culture = $PSBPreference.Help.DefaultLocale 26 | } 27 | 28 | if ($PSBPreference.Help.ConvertReadMeToAboutHelp) { 29 | $readMePath = Get-ChildItem -Path $PSBPreference.General.ProjectRoot -Include 'readme.md', 'readme.markdown', 'readme.txt' -Depth 1 | 30 | Select-Object -First 1 31 | if ($readMePath) { 32 | $buildParams.ReadMePath = $readMePath 33 | } 34 | } 35 | 36 | # only add these configuration values to the build parameters if they have been been set 37 | 'CompileHeader', 'CompileFooter', 'CompileScriptHeader', 'CompileScriptFooter' | ForEach-Object { 38 | if ($PSBPreference.Build.Keys -contains $_) { 39 | $buildParams.$_ = $PSBPreference.Build.$_ 40 | } 41 | } 42 | 43 | Build-PSBuildModule @buildParams 44 | } 45 | 46 | 47 | 48 | $analyzePreReqs = { 49 | $result = $true 50 | if (-not $PSBPreference.Test.ScriptAnalysis.Enabled) { 51 | Write-Warning 'Script analysis is not enabled.' 52 | $result = $false 53 | } 54 | if (-not (Get-Module -Name PSScriptAnalyzer -ListAvailable)) { 55 | Write-Warning 'PSScriptAnalyzer module is not installed' 56 | $result = $false 57 | } 58 | $result 59 | } 60 | 61 | # Synopsis: Execute PSScriptAnalyzer tests 62 | task Analyze -If (. $analyzePreReqs) Build,{ 63 | $analyzeParams = @{ 64 | Path = $PSBPreference.Build.ModuleOutDir 65 | SeverityThreshold = $PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel 66 | SettingsPath = $PSBPreference.Test.ScriptAnalysis.SettingsPath 67 | } 68 | Test-PSBuildScriptAnalysis @analyzeParams 69 | } 70 | 71 | $pesterPreReqs = { 72 | $result = $true 73 | if (-not $PSBPreference.Test.Enabled) { 74 | Write-Warning 'Pester testing is not enabled.' 75 | $result = $false 76 | } 77 | if (-not (Get-Module -Name Pester -ListAvailable)) { 78 | Write-Warning 'Pester module is not installed' 79 | $result = $false 80 | } 81 | if (-not (Test-Path -Path $PSBPreference.Test.RootDir)) { 82 | Write-Warning "Test directory [$($PSBPreference.Test.RootDir)] not found" 83 | $result = $false 84 | } 85 | return $result 86 | } 87 | 88 | # Synopsis: Execute Pester tests 89 | task Pester -If (. $pesterPreReqs) Build,{ 90 | $pesterParams = @{ 91 | Path = $PSBPreference.Test.RootDir 92 | ModuleName = $PSBPreference.General.ModuleName 93 | ModuleManifest = Join-Path $PSBPreference.Build.ModuleOutDir "$($PSBPreference.General.ModuleName).psd1" 94 | OutputPath = $PSBPreference.Test.OutputFile 95 | OutputFormat = $PSBPreference.Test.OutputFormat 96 | CodeCoverage = $PSBPreference.Test.CodeCoverage.Enabled 97 | CodeCoverageThreshold = $PSBPreference.Test.CodeCoverage.Threshold 98 | CodeCoverageFiles = $PSBPreference.Test.CodeCoverage.Files 99 | CodeCoverageOutputFile = $PSBPreference.Test.CodeCoverage.OutputFile 100 | CodeCoverageOutputFileFormat = $PSBPreference.Test.CodeCoverage.OutputFormat 101 | ImportModule = $PSBPreference.Test.ImportModule 102 | SkipRemainingOnFailure = $PSBPreference.Test.SkipRemainingOnFailure 103 | OutputVerbosity = $PSBPreference.Test.OutputVerbosity 104 | } 105 | Test-PSBuildPester @pesterParams 106 | } 107 | 108 | 109 | 110 | $genMarkdownPreReqs = { 111 | $result = $true 112 | if (-not (Get-Module platyPS -ListAvailable)) { 113 | Write-Warning "platyPS module is not installed. Skipping [$($task.name)] task." 114 | $result = $false 115 | } 116 | $result 117 | } 118 | 119 | # Synopsis: Generates PlatyPS markdown files from module help 120 | task GenerateMarkdown -if (. $genMarkdownPreReqs) StageFiles,{ 121 | $buildMDParams = @{ 122 | ModulePath = $PSBPreference.Build.ModuleOutDir 123 | ModuleName = $PSBPreference.General.ModuleName 124 | DocsPath = $PSBPreference.Docs.RootDir 125 | Locale = $PSBPreference.Help.DefaultLocale 126 | Overwrite = $PSBPreference.Docs.Overwrite 127 | AlphabeticParamsOrder = $PSBPreference.Docs.AlphabeticParamsOrder 128 | ExcludeDontShow = $PSBPreference.Docs.ExcludeDontShow 129 | UseFullTypeName = $PSBPreference.Docs.UseFullTypeName 130 | } 131 | Build-PSBuildMarkdown @buildMDParams 132 | } 133 | 134 | $genHelpFilesPreReqs = { 135 | $result = $true 136 | if (-not (Get-Module platyPS -ListAvailable)) { 137 | Write-Warning "platyPS module is not installed. Skipping [$($task.name)] task." 138 | $result = $false 139 | } 140 | $result 141 | } 142 | 143 | # Synopsis: Generates MAML-based help from PlatyPS markdown files 144 | task GenerateMAML -if (. $genHelpFilesPreReqs) GenerateMarkdown, { 145 | Build-PSBuildMAMLHelp -Path $PSBPreference.Docs.RootDir -DestinationPath $PSBPreference.Build.ModuleOutDir 146 | } 147 | 148 | $genUpdatableHelpPreReqs = { 149 | $result = $true 150 | if (-not (Get-Module platyPS -ListAvailable)) { 151 | Write-Warning "platyPS module is not installed. Skipping [$($task.name)] task." 152 | $result = $false 153 | } 154 | $result 155 | } 156 | 157 | # Synopsis: Create updatable help .cab file based on PlatyPS markdown help 158 | task GenerateUpdatableHelp -if (. $genUpdatableHelpPreReqs) BuildHelp, { 159 | Build-PSBuildUpdatableHelp -DocsPath $PSBPreference.Docs.RootDir -OutputPath $PSBPreference.Help.UpdatableHelpOutDir 160 | } 161 | 162 | # Synopsis: Publish module to the defined PowerShell repository 163 | Task Publish Test, { 164 | Assert ($PSBPreference.Publish.PSRepositoryApiKey -or $PSBPreference.Publish.PSRepositoryCredential) "API key or credential not defined to authenticate with [$($PSBPreference.Publish.PSRepository)] with." 165 | 166 | $publishParams = @{ 167 | Path = $PSBPreference.Build.ModuleOutDir 168 | Version = $PSBPreference.General.ModuleVersion 169 | Repository = $PSBPreference.Publish.PSRepository 170 | Verbose = $true #$VerbosePreference 171 | } 172 | if ($PSBPreference.Publish.PSRepositoryApiKey) { 173 | $publishParams.ApiKey = $PSBPreference.Publish.PSRepositoryApiKey 174 | } 175 | 176 | if ($PSBPreference.Publish.PSRepositoryCredential) { 177 | $publishParams.Credential = $PSBPreference.Publish.PSRepositoryCredential 178 | } 179 | 180 | Publish-PSBuildModule @publishParams 181 | } 182 | 183 | 184 | #region Summary Tasks 185 | 186 | # Synopsis: Builds help documentation 187 | task BuildHelp GenerateMarkdown,GenerateMAML 188 | 189 | Task Build { 190 | if ([String]$PSBPreference.Build.Dependencies -ne [String]$__DefaultBuildDependencies) { 191 | throw [NotSupportedException]'You cannot use $PSBPreference.Build.Dependencies with Invoke-Build. Please instead redefine the build task or your default task to include your dependencies. Example: Task . Dependency1,Dependency2,Build,Test or Task Build Dependency1,Dependency2,StageFiles' 192 | } 193 | },StageFiles,BuildHelp 194 | 195 | # Synopsis: Execute Pester and ScriptAnalyzer tests 196 | task Test Analyze,Pester 197 | 198 | task . Build,Test 199 | 200 | #endregion Summary Tasks 201 | -------------------------------------------------------------------------------- /PowerShellBuild/PowerShellBuild.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | RootModule = 'PowerShellBuild.psm1' 3 | ModuleVersion = '0.7.2' 4 | GUID = '15431eb8-be2d-4154-b8ad-4cb68a488e3d' 5 | Author = 'Brandon Olin' 6 | CompanyName = 'Community' 7 | Copyright = '(c) Brandon Olin. All rights reserved.' 8 | Description = 'A common psake and Invoke-Build task module for PowerShell projects' 9 | PowerShellVersion = '3.0' 10 | RequiredModules = @( 11 | @{ModuleName = 'BuildHelpers'; ModuleVersion = '2.0.16' } 12 | @{ModuleName = 'Pester'; ModuleVersion = '5.6.1' } 13 | @{ModuleName = 'platyPS'; ModuleVersion = '0.14.1' } 14 | @{ModuleName = 'psake'; ModuleVersion = '4.9.0' } 15 | ) 16 | FunctionsToExport = @( 17 | 'Build-PSBuildMAMLHelp' 18 | 'Build-PSBuildMarkdown' 19 | 'Build-PSBuildModule' 20 | 'Build-PSBuildUpdatableHelp' 21 | 'Clear-PSBuildOutputFolder' 22 | 'Initialize-PSBuild' 23 | 'Publish-PSBuildModule' 24 | 'Test-PSBuildPester' 25 | 'Test-PSBuildScriptAnalysis' 26 | ) 27 | CmdletsToExport = @() 28 | VariablesToExport = @() 29 | AliasesToExport = @('*tasks') 30 | PrivateData = @{ 31 | PSData = @{ 32 | Tags = @('psake', 'build', 'InvokeBuild') 33 | LicenseUri = 'https://raw.githubusercontent.com/psake/PowerShellBuild/master/LICENSE' 34 | ProjectUri = 'https://github.com/psake/PowerShellBuild' 35 | IconUri = 'https://raw.githubusercontent.com/psake/PowerShellBuild/master/media/psaketaskmodule-256x256.png' 36 | ReleaseNotes = 'https://raw.githubusercontent.com/psake/PowerShellBuild/master/CHANGELOG.md' 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PowerShellBuild/PowerShellBuild.psm1: -------------------------------------------------------------------------------- 1 | # Dot source public functions 2 | $private = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Private/*.ps1')) -Recurse) 3 | $public = @(Get-ChildItem -Path ([IO.Path]::Combine($PSScriptRoot, 'Public/*.ps1')) -Recurse) 4 | foreach ($import in $public + $private) { 5 | try { 6 | . $import.FullName 7 | } catch { 8 | throw "Unable to dot source [$($import.FullName)]" 9 | } 10 | } 11 | 12 | Export-ModuleMember -Function $public.Basename 13 | 14 | # $psakeTaskAlias = 'PowerShellBuild.psake.tasks' 15 | # Set-Alias -Name $psakeTaskAlias -Value $PSScriptRoot/psakeFile.ps1 16 | # Export-ModuleMember -Alias $psakeTaskAlias 17 | 18 | # Invoke-Build task aliases 19 | $ibAlias = 'PowerShellBuild.IB.Tasks' 20 | Set-Alias -Name $ibAlias -Value $PSScriptRoot/IB.tasks.ps1 21 | Export-ModuleMember -Alias $ibAlias 22 | -------------------------------------------------------------------------------- /PowerShellBuild/Private/Remove-ExcludedItem.ps1: -------------------------------------------------------------------------------- 1 | function Remove-ExcludedItem { 2 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] 3 | [cmdletbinding()] 4 | [OutputType([IO.FileSystemInfo[]])] 5 | param( 6 | [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 7 | [Alias('PSPath', 'FullName')] 8 | [AllowEmptyCollection()] 9 | [IO.FileSystemInfo[]]$InputObject, 10 | 11 | [string[]]$Exclude 12 | ) 13 | 14 | begin { 15 | $keepers = [Collections.Generic.List[IO.FileSystemInfo]]::new() 16 | } 17 | 18 | process { 19 | :item 20 | foreach ($item in $InputObject) { 21 | foreach ($regex in $Exclude) { 22 | if ($_ -match $regex) { 23 | break item 24 | } 25 | } 26 | $keepers.Add($_) 27 | } 28 | } 29 | 30 | end { 31 | $keepers 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Build-PSBuildMAMLHelp.ps1: -------------------------------------------------------------------------------- 1 | function Build-PSBuildMAMLHelp { 2 | <# 3 | .SYNOPSIS 4 | Builds PowerShell MAML XML help file from PlatyPS markdown files 5 | .DESCRIPTION 6 | Builds PowerShell MAML XML help file from PlatyPS markdown files 7 | .PARAMETER Path 8 | The path to the PlatyPS markdown documents. 9 | .PARAMETER DestinationPath 10 | The path to the output module directory. 11 | .EXAMPLE 12 | PS> Build-PSBuildMAMLHelp -Path ./docs -Destination ./output/MyModule 13 | 14 | Uses PlatyPS to generate MAML XML help from markdown files in ./docs 15 | and saves the XML file to a directory under ./output/MyModule 16 | #> 17 | [cmdletbinding()] 18 | param( 19 | [parameter(Mandatory)] 20 | [string]$Path, 21 | 22 | [parameter(Mandatory)] 23 | [string]$DestinationPath 24 | ) 25 | 26 | $helpLocales = (Get-ChildItem -Path $Path -Directory).Name 27 | 28 | # Generate the module's primary MAML help file 29 | foreach ($locale in $helpLocales) { 30 | $externalHelpParams = @{ 31 | Path = [IO.Path]::Combine($Path, $locale) 32 | OutputPath = [IO.Path]::Combine($DestinationPath, $locale) 33 | Force = $true 34 | ErrorAction = 'SilentlyContinue' 35 | Verbose = $VerbosePreference 36 | } 37 | New-ExternalHelp @externalHelpParams > $null 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Build-PSBuildMarkdown.ps1: -------------------------------------------------------------------------------- 1 | function Build-PSBuildMarkdown { 2 | <# 3 | .SYNOPSIS 4 | Creates PlatyPS markdown documents based on comment-based help of module. 5 | .DESCRIPTION 6 | Creates PlatyPS markdown documents based on comment-based help of module. 7 | .PARAMETER ModulePath 8 | The path to the module 9 | .PARAMETER ModuleName 10 | The name of the module. 11 | .PARAMETER DocsPath 12 | The path where PlatyPS markdown docs will be saved. 13 | .PARAMETER Locale 14 | The locale to save the markdown docs. 15 | .PARAMETER Overwrite 16 | Overwrite existing markdown files and use comment based help as the source of truth. 17 | .PARAMETER AlphabeticParamsOrder 18 | Order parameters alphabetically by name in PARAMETERS section. There are 5 exceptions: -Confirm, -WhatIf, -IncludeTotalCount, -Skip, and -First parameters will be the last. 19 | .PARAMETER ExcludeDontShow 20 | Exclude the parameters marked with `DontShow` in the parameter attribute from the help content. 21 | .PARAMETER UseFullTypeName 22 | Indicates that the target document will use a full type name instead of a short name for parameters. 23 | .EXAMPLE 24 | PS> Build-PSBuildMarkdown -ModulePath ./output/MyModule/0.1.0 -ModuleName MyModule -DocsPath ./docs -Locale en-US 25 | 26 | Analysis the comment-based help of the MyModule module and create markdown documents under ./docs/en-US. 27 | #> 28 | [cmdletbinding()] 29 | param( 30 | [parameter(Mandatory)] 31 | [string]$ModulePath, 32 | 33 | [parameter(Mandatory)] 34 | [string]$ModuleName, 35 | 36 | [parameter(Mandatory)] 37 | [string]$DocsPath, 38 | 39 | [parameter(Mandatory)] 40 | [string]$Locale, 41 | 42 | [parameter(Mandatory)] 43 | [bool]$Overwrite, 44 | 45 | [parameter(Mandatory)] 46 | [bool]$AlphabeticParamsOrder, 47 | 48 | [parameter(Mandatory)] 49 | [bool]$ExcludeDontShow, 50 | 51 | [parameter(Mandatory)] 52 | [bool]$UseFullTypeName 53 | ) 54 | 55 | $moduleInfo = Import-Module "$ModulePath/$ModuleName.psd1" -Global -Force -PassThru 56 | 57 | try { 58 | if ($moduleInfo.ExportedCommands.Count -eq 0) { 59 | Write-Warning 'No commands have been exported. Skipping markdown generation.' 60 | return 61 | } 62 | 63 | if (-not (Test-Path -LiteralPath $DocsPath)) { 64 | New-Item -Path $DocsPath -ItemType Directory > $null 65 | } 66 | 67 | if (Get-ChildItem -LiteralPath $DocsPath -Filter *.md -Recurse) { 68 | $updateMDParams = @{ 69 | AlphabeticParamsOrder = $AlphabeticParamsOrder 70 | ExcludeDontShow = $ExcludeDontShow 71 | UseFullTypeName = $UseFullTypeName 72 | Verbose = $VerbosePreference 73 | } 74 | Get-ChildItem -LiteralPath $DocsPath -Directory | ForEach-Object { 75 | Update-MarkdownHelp -Path $_.FullName @updateMDParams > $null 76 | } 77 | } 78 | 79 | # ErrorAction set to SilentlyContinue so this command will not overwrite an existing MD file. 80 | $newMDParams = @{ 81 | Module = $ModuleName 82 | Locale = $Locale 83 | OutputFolder = [IO.Path]::Combine($DocsPath, $Locale) 84 | AlphabeticParamsOrder = $AlphabeticParamsOrder 85 | ExcludeDontShow = $ExcludeDontShow 86 | UseFullTypeName = $UseFullTypeName 87 | ErrorAction = 'SilentlyContinue' 88 | Verbose = $VerbosePreference 89 | } 90 | if ($Overwrite) { 91 | $newMDParams.Add('Force', $true) 92 | $newMDParams.Remove('ErrorAction') 93 | } 94 | New-MarkdownHelp @newMDParams > $null 95 | } catch { 96 | Write-Error "Failed to generate markdown help. : $_" 97 | } finally { 98 | Remove-Module $moduleName 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Build-PSBuildModule.ps1: -------------------------------------------------------------------------------- 1 | function Build-PSBuildModule { 2 | <# 3 | .SYNOPSIS 4 | Builds a PowerShell module based on source directory 5 | .DESCRIPTION 6 | Builds a PowerShell module based on source directory and optionally concatenates 7 | public/private functions from separete files into monolithic .PSM1 file. 8 | .PARAMETER Path 9 | The source module path. 10 | .PARAMETER DestinationPath 11 | Destination path to write "built" module to. 12 | .PARAMETER ModuleName 13 | The name of the module. 14 | .PARAMETER Compile 15 | Switch to indicate if separete function files should be concatenated into monolithic .PSM1 file. 16 | .PARAMETER CompileHeader 17 | String that will be at the top of your PSM1 file. 18 | .PARAMETER CompileFooter 19 | String that will be added to the bottom of your PSM1 file. 20 | .PARAMETER CompileScriptHeader 21 | String that will be added to your PSM1 file before each script file. 22 | .PARAMETER CompileScriptFooter 23 | String that will be added to your PSM1 file beforeafter each script file. 24 | .PARAMETER ReadMePath 25 | Path to project README. If present, this will become the "about_.help.txt" file in the build module. 26 | .PARAMETER CompileDirectories 27 | List of directories containing .ps1 files that will also be compiled into the PSM1. 28 | .PARAMETER CopyDirectories 29 | List of directories that will copying "as-is" into the build module. 30 | .PARAMETER Exclude 31 | Array of files (regular expressions) to exclude from copying into built module. 32 | .PARAMETER Culture 33 | UI Culture. This is used to determine what culture directory to store "about_.help.txt" in. 34 | .EXAMPLE 35 | PS> $buildParams = @{ 36 | Path = ./MyModule 37 | DestinationPath = ./Output/MoModule/0.1.0 38 | ModuleName = MyModule 39 | Exclude = @() 40 | Compile = $false 41 | Culture = (Get-UICulture).Name 42 | } 43 | PS> Build-PSBuildModule @buildParams 44 | 45 | Build module from source directory './MyModule' and save to '/Output/MoModule/0.1.0' 46 | #> 47 | [cmdletbinding()] 48 | param( 49 | [parameter(Mandatory)] 50 | [string]$Path, 51 | 52 | [parameter(Mandatory)] 53 | [string]$DestinationPath, 54 | 55 | [parameter(Mandatory)] 56 | [string]$ModuleName, 57 | 58 | [switch]$Compile, 59 | 60 | [string]$CompileHeader, 61 | 62 | [string]$CompileFooter, 63 | 64 | [string]$CompileScriptHeader, 65 | 66 | [string]$CompileScriptFooter, 67 | 68 | [string]$ReadMePath, 69 | 70 | [string[]]$CompileDirectories = @(), 71 | 72 | [string[]]$CopyDirectories = @(), 73 | 74 | [string[]]$Exclude = @(), 75 | 76 | [string]$Culture = (Get-UICulture).Name 77 | ) 78 | 79 | if (-not (Test-Path -LiteralPath $DestinationPath)) { 80 | New-Item -Path $DestinationPath -ItemType Directory -Verbose:$VerbosePreference > $null 81 | } 82 | 83 | # Copy "non-processed files" 84 | Get-ChildItem -Path $Path -Include '*.psm1', '*.psd1', '*.ps1xml' -Depth 1 | Copy-Item -Destination $DestinationPath -Force 85 | foreach ($dir in $CopyDirectories) { 86 | $copyPath = [IO.Path]::Combine($Path, $dir) 87 | Copy-Item -Path $copyPath -Destination $DestinationPath -Recurse -Force 88 | } 89 | 90 | # Copy README as about_.help.txt 91 | if (-not [string]::IsNullOrEmpty($ReadMePath)) { 92 | $culturePath = [IO.Path]::Combine($DestinationPath, $Culture) 93 | $aboutModulePath = [IO.Path]::Combine($culturePath, "about_$($ModuleName).help.txt") 94 | if(-not (Test-Path $culturePath -PathType Container)) { 95 | New-Item $culturePath -Type Directory -Force > $null 96 | Copy-Item -LiteralPath $ReadMePath -Destination $aboutModulePath -Force 97 | } 98 | } 99 | 100 | # Copy source files to destination and optionally combine *.ps1 files into the PSM1 101 | if ($Compile.IsPresent) { 102 | $rootModule = [IO.Path]::Combine($DestinationPath, "$ModuleName.psm1") 103 | 104 | # Grab the contents of the copied over PSM1 105 | # This will be appended to the end of the finished PSM1 106 | $psm1Contents = Get-Content -Path $rootModule -Raw 107 | '' | Out-File -FilePath $rootModule -Encoding utf8 108 | 109 | if ($CompileHeader) { 110 | $CompileHeader | Add-Content -Path $rootModule -Encoding utf8 111 | } 112 | 113 | $resolvedCompileDirectories = $CompileDirectories | ForEach-Object { 114 | [IO.Path]::Combine($Path, $_) 115 | } 116 | $allScripts = Get-ChildItem -Path $resolvedCompileDirectories -Filter '*.ps1' -File -Recurse -ErrorAction SilentlyContinue 117 | 118 | $allScripts = $allScripts | Remove-ExcludedItem -Exclude $Exclude 119 | 120 | $allScripts | ForEach-Object { 121 | $srcFile = Resolve-Path $_.FullName -Relative 122 | Write-Verbose "Adding [$srcFile] to PSM1" 123 | 124 | if ($CompileScriptHeader) { 125 | Write-Output $CompileScriptHeader 126 | } 127 | 128 | Get-Content $srcFile 129 | 130 | if ($CompileScriptFooter) { 131 | Write-Output $CompileScriptFooter 132 | } 133 | 134 | } | Add-Content -Path $rootModule -Encoding utf8 135 | 136 | $psm1Contents | Add-Content -Path $rootModule -Encoding utf8 137 | 138 | if ($CompileFooter) { 139 | $CompileFooter | Add-Content -Path $rootModule -Encoding utf8 140 | } 141 | } else{ 142 | # Copy everything over, then remove stuff that should have been excluded 143 | # It's just easier this way 144 | $copyParams = @{ 145 | Path = [IO.Path]::Combine($Path, '*') 146 | Destination = $DestinationPath 147 | Recurse = $true 148 | Force = $true 149 | Verbose = $VerbosePreference 150 | } 151 | Copy-Item @copyParams 152 | $allItems = Get-ChildItem -Path $DestinationPath -Recurse 153 | $toRemove = foreach ($item in $allItems) { 154 | foreach ($regex in $Exclude) { 155 | if ($item -match $regex) { 156 | $item 157 | } 158 | } 159 | } 160 | $toRemove | Remove-Item -Recurse -Force -ErrorAction Ignore 161 | } 162 | 163 | # Export public functions in manifest if there are any public functions 164 | $publicFunctions = Get-ChildItem $Path/Public/*.ps1 -Recurse -ErrorAction SilentlyContinue 165 | if ($publicFunctions) { 166 | $outputManifest = [IO.Path]::Combine($DestinationPath, "$ModuleName.psd1") 167 | Update-Metadata -Path $OutputManifest -PropertyName FunctionsToExport -Value $publicFunctions.BaseName 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1: -------------------------------------------------------------------------------- 1 | function Build-PSBuildUpdatableHelp { 2 | <# 3 | .SYNOPSIS 4 | Create updatable help .cab file based on PlatyPS markdown help. 5 | .DESCRIPTION 6 | Create updatable help .cab file based on PlatyPS markdown help. 7 | .PARAMETER DocsPath 8 | Path to PlatyPS markdown help files. 9 | .PARAMETER OutputPath 10 | Path to create updatable help .cab file in. 11 | .PARAMETER Module 12 | Name of the module to create a .cab file for. Defaults to the $ModuleName variable from the parent scope. 13 | .EXAMPLE 14 | PS> Build-PSBuildUpdatableHelp -DocsPath ./docs -OutputPath ./Output/UpdatableHelp 15 | 16 | Create help .cab file based on PlatyPS markdown help. 17 | #> 18 | [cmdletbinding()] 19 | param( 20 | [parameter(Mandatory)] 21 | [string]$DocsPath, 22 | 23 | [parameter(Mandatory)] 24 | [string]$OutputPath, 25 | 26 | [string]$Module = $ModuleName 27 | ) 28 | 29 | if ($null -ne $IsWindows -and -not $IsWindows) { 30 | Write-Warning 'MakeCab.exe is only available on Windows. Cannot create help cab.' 31 | return 32 | } 33 | 34 | $helpLocales = (Get-ChildItem -Path $DocsPath -Directory).Name 35 | 36 | # Create updatable help output directory 37 | if (-not (Test-Path -LiteralPath $OutputPath)) { 38 | New-Item $OutputPath -ItemType Directory -Verbose:$VerbosePreference > $null 39 | } else { 40 | Write-Verbose "Directory already exists [$OutputPath]." 41 | Get-ChildItem $OutputPath | Remove-Item -Recurse -Force -Verbose:$VerbosePreference 42 | } 43 | 44 | # Generate updatable help files. Note: this will currently update the version number in the module's MD 45 | # file in the metadata. 46 | foreach ($locale in $helpLocales) { 47 | $cabParams = @{ 48 | CabFilesFolder = [IO.Path]::Combine($moduleOutDir, $locale) 49 | LandingPagePath = [IO.Path]::Combine($DocsPath, $locale, "$Module.md") 50 | OutputFolder = $OutputPath 51 | Verbose = $VerbosePreference 52 | } 53 | New-ExternalHelpCab @cabParams > $null 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Clear-PSBuildOutputFolder.ps1: -------------------------------------------------------------------------------- 1 | function Clear-PSBuildOutputFolder { 2 | <# 3 | .SYNOPSIS 4 | Clears module output directory. 5 | .DESCRIPTION 6 | Clears module output directory. 7 | .PARAMETER Path 8 | Module output path to remove. 9 | .EXAMPLE 10 | PS> Clear-PSBuildOutputFolder -Path ./Output/MyModule/0.1.0 11 | 12 | Removes the './Output/MyModule/0.1.0' directory. 13 | #> 14 | [CmdletBinding()] 15 | param( 16 | # Maybe a bit paranoid but this task nuked \ on my laptop. Good thing I was not running as admin. 17 | [parameter(Mandatory)] 18 | [ValidateScript({ 19 | if ($_.Length -le 3) { 20 | throw "`$Path [$_] must be longer than 3 characters." 21 | } 22 | $true 23 | })] 24 | [string]$Path 25 | ) 26 | 27 | if (Test-Path -Path $Path) { 28 | Remove-Item -Path $Path -Recurse -Force -Verbose:$false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Initialize-PSBuild.ps1: -------------------------------------------------------------------------------- 1 | function Initialize-PSBuild { 2 | <# 3 | .SYNOPSIS 4 | Initializes BuildHelpers to populate build environment variables. 5 | .DESCRIPTION 6 | Initializes BuildHelpers to populate build environment variables. 7 | .PARAMETER BuildEnvironment 8 | Contains the PowerShellBuild settings (known as $PSBPreference). 9 | .PARAMETER UseBuildHelpers 10 | Use BuildHelpers module to populate common environment variables based on current build system context. 11 | .EXAMPLE 12 | PS> Initialize-PSBuild -UseBuildHelpers 13 | 14 | Populate build system environment variables. 15 | #> 16 | [cmdletbinding()] 17 | param( 18 | [Parameter(Mandatory)] 19 | [Hashtable] 20 | $BuildEnvironment, 21 | 22 | [switch]$UseBuildHelpers 23 | ) 24 | 25 | if ($BuildEnvironment.Build.OutDir.StartsWith($env:BHProjectPath, [StringComparison]::OrdinalIgnoreCase)) { 26 | $BuildEnvironment.Build.ModuleOutDir = [IO.Path]::Combine($BuildEnvironment.Build.OutDir, $env:BHProjectName, $BuildEnvironment.General.ModuleVersion) 27 | } else { 28 | $BuildEnvironment.Build.ModuleOutDir = [IO.Path]::Combine($env:BHProjectPath, $BuildEnvironment.Build.OutDir, $env:BHProjectName, $BuildEnvironment.General.ModuleVersion) 29 | } 30 | 31 | $params = @{ 32 | BuildOutput = $BuildEnvironment.Build.ModuleOutDir 33 | } 34 | Set-BuildEnvironment @params -Force 35 | 36 | Write-Host 'Build System Details:' -ForegroundColor Yellow 37 | $psVersion = $PSVersionTable.PSVersion.ToString() 38 | $buildModuleName = $MyInvocation.MyCommand.Module.Name 39 | $buildModuleVersion = $MyInvocation.MyCommand.Module.Version 40 | "Build Module: $buildModuleName`:$buildModuleVersion" 41 | "PowerShell Version: $psVersion" 42 | 43 | if ($UseBuildHelpers.IsPresent) { 44 | $nl = [System.Environment]::NewLine 45 | 46 | Write-Host "$nl`Environment variables:" -ForegroundColor Yellow 47 | (Get-Item ENV:BH*).Foreach({ 48 | '{0,-20}{1}' -f $_.name, $_.value 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Publish-PSBuildModule.ps1: -------------------------------------------------------------------------------- 1 | function Publish-PSBuildModule { 2 | <# 3 | .SYNOPSIS 4 | Publishes a module to the defined PowerShell repository. 5 | .DESCRIPTION 6 | Publishes a module to the defined PowerShell repository. 7 | .PARAMETER Path 8 | The path to the module to publish. 9 | .PARAMETER Version 10 | The version of the module to publish. 11 | .PARAMETER Repository 12 | The PowerShell repository name to publish to. 13 | .PARAMETER NuGetApiKey 14 | The API key to use to authenticate to the PowerShell repository with. 15 | .PARAMETER Credential 16 | The credential to use to authenticate to the PowerShell repository with. 17 | .EXAMPLE 18 | PS> Publish-PSBuildModule -Path .\Output\0.1.0\MyModule -Version 0.1.0 -Repository PSGallery -NuGetApiKey 12345 19 | 20 | Publish version 0.1.0 of the module at path .\Output\0.1.0\MyModule to the PSGallery repository using an API key. 21 | .EXAMPLE 22 | PS> Publish-PSBuildModule -Path .\Output\0.1.0\MyModule -Version 0.1.0 -Repository PSGallery -Credential $myCred 23 | 24 | Publish version 0.1.0 of the module at path .\Output\0.1.0\MyModule to the PSGallery repository using a PowerShell credential. 25 | .EXAMPLE 26 | PS> Publish-PSBuildModule -Path .\Output\0.1.0\MyModule -Version 0.1.0 -Repository PSGallery -NuGetApiKey 12345 -Credential $myCred 27 | 28 | Publish version 0.1.0 of the module at path .\Output\0.1.0\MyModule to the PSGallery repository using an API key and a PowerShell credential. 29 | #> 30 | [cmdletbinding(DefaultParameterSetName = 'ApiKey')] 31 | param( 32 | [parameter(Mandatory)] 33 | [ValidateScript({ 34 | if (-not (Test-Path -Path $_ )) { 35 | throw 'Folder does not exist' 36 | } 37 | if (-not (Test-Path -Path $_ -PathType Container)) { 38 | throw 'The Path argument must be a folder. File paths are not allowed.' 39 | } 40 | $true 41 | })] 42 | [System.IO.FileInfo]$Path, 43 | 44 | [parameter(Mandatory)] 45 | [string]$Version, 46 | 47 | [parameter(Mandatory)] 48 | [string]$Repository, 49 | 50 | [Alias('ApiKey')] 51 | [string]$NuGetApiKey, 52 | 53 | [pscredential]$Credential 54 | ) 55 | 56 | Write-Verbose "Publishing version [$Version] to repository [$Repository]..." 57 | 58 | $publishParams = @{ 59 | Path = $Path 60 | Repository = $Repository 61 | Verbose = $VerbosePreference 62 | } 63 | 64 | 'NuGetApiKey', 'Credential' | ForEach-Object { 65 | if ($PSBoundParameters.ContainsKey($_)) { 66 | $publishParams.$_ = $PSBoundParameters.$_ 67 | } 68 | } 69 | 70 | Publish-Module @publishParams 71 | } 72 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Test-PSBuildPester.ps1: -------------------------------------------------------------------------------- 1 | function Test-PSBuildPester { 2 | <# 3 | .SYNOPSIS 4 | Execute Pester tests for module. 5 | .DESCRIPTION 6 | Execute Pester tests for module. 7 | .PARAMETER Path 8 | Directory Pester tests to execute. 9 | .PARAMETER ModuleName 10 | Name of Module to test. 11 | .PARAMETER ModuleManifest 12 | Path to module manifest to import during test 13 | .PARAMETER OutputPath 14 | Output path to store Pester test results to. 15 | .PARAMETER OutputFormat 16 | Test result output format (NUnit). 17 | .PARAMETER CodeCoverage 18 | Switch to indicate that code coverage should be calculated. 19 | .PARAMETER CodeCoverageThreshold 20 | Threshold required to pass code coverage test (.90 = 90%). 21 | .PARAMETER CodeCoverageFiles 22 | Array of files to validate code coverage for. 23 | .PARAMETER CodeCoverageOutputFile 24 | Output path (relative to Pester tests directory) to store code coverage results to. 25 | .PARAMETER CodeCoverageOutputFileFormat 26 | Code coverage result output format. Currently, only 'JaCoCo' is supported by Pester. 27 | .PARAMETER ImportModule 28 | Import module from OutDir prior to running Pester tests. 29 | .PARAMETER SkipRemainingOnFailure 30 | Skip remaining tests after failure for selected scope. Options are None, Run, Container and Block. Default: None. 31 | .PARAMETER OutputVerbosity 32 | The verbosity of output, options are None, Normal, Detailed and Diagnostic. Default is Detailed. 33 | .EXAMPLE 34 | PS> Test-PSBuildPester -Path ./tests -ModuleName Mymodule -OutputPath ./out/testResults.xml 35 | 36 | Run Pester tests in ./tests and save results to ./out/testResults.xml 37 | #> 38 | [cmdletbinding()] 39 | param( 40 | [parameter(Mandatory)] 41 | [string]$Path, 42 | 43 | [string]$ModuleName, 44 | 45 | [string]$ModuleManifest, 46 | 47 | [string]$OutputPath, 48 | 49 | [string]$OutputFormat = 'NUnit2.5', 50 | 51 | [switch]$CodeCoverage, 52 | 53 | [double]$CodeCoverageThreshold, 54 | 55 | [string[]]$CodeCoverageFiles = @(), 56 | 57 | [string]$CodeCoverageOutputFile = 'coverage.xml', 58 | 59 | [string]$CodeCoverageOutputFileFormat = 'JaCoCo', 60 | 61 | [switch]$ImportModule, 62 | 63 | [ValidateSet('None', 'Run', 'Container', 'Block')] 64 | [string]$SkipRemainingOnFailure = 'None', 65 | 66 | [ValidateSet('None', 'Normal', 'Detailed', 'Diagnostic')] 67 | [string]$OutputVerbosity = 'Detailed' 68 | ) 69 | 70 | if (-not (Get-Module -Name Pester)) { 71 | Import-Module -Name Pester -ErrorAction Stop 72 | } 73 | 74 | try { 75 | if ($ImportModule) { 76 | if (-not (Test-Path $ModuleManifest)) { 77 | Write-Error "Unable to find module manifest [$ModuleManifest]. Can't import module" 78 | } else { 79 | # Remove any previously imported project modules and import from the output dir 80 | Get-Module $ModuleName | Remove-Module -Force -ErrorAction SilentlyContinue 81 | Import-Module $ModuleManifest -Force 82 | } 83 | } 84 | 85 | Push-Location -LiteralPath $Path 86 | 87 | Import-Module Pester -MinimumVersion 5.0.0 88 | $configuration = [PesterConfiguration]::Default 89 | $configuration.Output.Verbosity = $OutputVerbosity 90 | $configuration.Run.PassThru = $true 91 | $configuration.Run.SkipRemainingOnFailure = $SkipRemainingOnFailure 92 | $configuration.TestResult.Enabled = -not [string]::IsNullOrEmpty($OutputPath) 93 | $configuration.TestResult.OutputPath = $OutputPath 94 | $configuration.TestResult.OutputFormat = $OutputFormat 95 | 96 | if ($CodeCoverage.IsPresent) { 97 | $configuration.CodeCoverage.Enabled = $true 98 | if ($CodeCoverageFiles.Count -gt 0) { 99 | $configuration.CodeCoverage.Path = $CodeCoverageFiles 100 | } 101 | $configuration.CodeCoverage.OutputPath = $CodeCoverageOutputFile 102 | $configuration.CodeCoverage.OutputFormat = $CodeCoverageOutputFileFormat 103 | } 104 | 105 | $testResult = Invoke-Pester -Configuration $configuration -Verbose:$VerbosePreference 106 | 107 | if ($testResult.FailedCount -gt 0) { 108 | throw 'One or more Pester tests failed' 109 | } 110 | 111 | if ($CodeCoverage.IsPresent) { 112 | Write-Host "`nCode Coverage:`n" -ForegroundColor Yellow 113 | if (Test-Path $CodeCoverageOutputFile) { 114 | $textInfo = (Get-Culture).TextInfo 115 | [xml]$testCoverage = Get-Content $CodeCoverageOutputFile 116 | $ccReport = $testCoverage.report.counter.ForEach({ 117 | $total = [int]$_.missed + [int]$_.covered 118 | $perc = [Math]::Truncate([int]$_.covered / $total) 119 | [pscustomobject]@{ 120 | name = $textInfo.ToTitleCase($_.Type.ToLower()) 121 | percent = $perc 122 | } 123 | }) 124 | 125 | $ccFailMsgs = @() 126 | $ccReport.ForEach({ 127 | 'Type: [{0}]: {1:p}' -f $_.name, $_.percent 128 | if ($_.percent -lt $CodeCoverageThreshold) { 129 | $ccFailMsgs += ('Code coverage: [{0}] is [{1:p}], which is less than the threshold of [{2:p}]' -f $_.name, $_.percent, $CodeCoverageThreshold) 130 | } 131 | }) 132 | Write-Host "`n" 133 | $ccFailMsgs.Foreach({ 134 | Write-Error $_ 135 | }) 136 | } else { 137 | Write-Error "Code coverage file [$CodeCoverageOutputFile] not found." 138 | } 139 | } 140 | } finally { 141 | Pop-Location 142 | Remove-Module $ModuleName -ErrorAction SilentlyContinue 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /PowerShellBuild/Public/Test-PSBuildScriptAnalysis.ps1: -------------------------------------------------------------------------------- 1 | function Test-PSBuildScriptAnalysis { 2 | <# 3 | .SYNOPSIS 4 | Run PSScriptAnalyzer tests against a module. 5 | .DESCRIPTION 6 | Run PSScriptAnalyzer tests against a module. 7 | .PARAMETER Path 8 | Path to PowerShell module directory to run ScriptAnalyser on. 9 | .PARAMETER SeverityThreshold 10 | Fail ScriptAnalyser test if any issues are found with this threshold or higher. 11 | .PARAMETER SettingsPath 12 | Path to ScriptAnalyser settings to use. 13 | .EXAMPLE 14 | PS> Test-PSBuildScriptAnalysis -Path ./Output/Mymodule/0.1.0 -SeverityThreshold Error 15 | 16 | Run ScriptAnalyzer on built module in ./Output/Mymodule/0.1.0. Throw error if any errors are found. 17 | #> 18 | [cmdletbinding()] 19 | param( 20 | [parameter(Mandatory)] 21 | [string]$Path, 22 | 23 | [ValidateSet('None', 'Error', 'Warning', 'Information')] 24 | [string]$SeverityThreshold, 25 | 26 | [string]$SettingsPath 27 | ) 28 | 29 | Write-Verbose "SeverityThreshold set to: $SeverityThreshold" 30 | 31 | $analysisResult = Invoke-ScriptAnalyzer -Path $Path -Settings $SettingsPath -Recurse -Verbose:$VerbosePreference 32 | $errors = ($analysisResult.where({$_Severity -eq 'Error'})).Count 33 | $warnings = ($analysisResult.where({$_Severity -eq 'Warning'})).Count 34 | $infos = ($analysisResult.where({$_Severity -eq 'Information'})).Count 35 | 36 | if ($analysisResult) { 37 | Write-Host 'PSScriptAnalyzer results:' -ForegroundColor Yellow 38 | $analysisResult | Format-Table -AutoSize 39 | } 40 | 41 | switch ($SeverityThreshold) { 42 | 'None' { 43 | return 44 | } 45 | 'Error' { 46 | if ($errors -gt 0) { 47 | throw 'One or more ScriptAnalyzer errors were found!' 48 | } 49 | } 50 | 'Warning' { 51 | if ($errors -gt 0 -or $warnings -gt 0) { 52 | throw 'One or more ScriptAnalyzer warnings were found!' 53 | } 54 | } 55 | 'Information' { 56 | if ($errors -gt 0 -or $warnings -gt 0 -or $infos -gt 0) { 57 | throw 'One or more ScriptAnalyzer warnings were found!' 58 | } 59 | } 60 | default { 61 | if ($analysisResult.Count -ne 0) { 62 | throw 'One or more ScriptAnalyzer issues were found!' 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /PowerShellBuild/ScriptAnalyzerSettings.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | ExcludeRules = @( 3 | 'PSAvoidUsingWriteHost', 4 | 'PSUseToExportFieldsInManifest' 5 | 'PSUseDeclaredVarsMoreThanAssignments' 6 | ) 7 | Rules = @{ 8 | # Don't trip on the task alias. It's by design 9 | PSAvoidUsingCmdletAliases = @{ 10 | Whitelist = @('task') 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PowerShellBuild/build.properties.ps1: -------------------------------------------------------------------------------- 1 | # spell-checker:ignore PSGALLERY BHPS MAML 2 | BuildHelpers\Set-BuildEnvironment -Force 3 | 4 | $outDir = [IO.Path]::Combine($env:BHProjectPath, 'Output') 5 | $moduleVersion = (Import-PowerShellDataFile -Path $env:BHPSModuleManifest).ModuleVersion 6 | 7 | [ordered]@{ 8 | General = @{ 9 | # Root directory for the project 10 | ProjectRoot = $env:BHProjectPath 11 | 12 | # Root directory for the module 13 | SrcRootDir = $env:BHPSModulePath 14 | 15 | # The name of the module. This should match the basename of the PSD1 file 16 | ModuleName = $env:BHProjectName 17 | 18 | # Module version 19 | ModuleVersion = $moduleVersion 20 | 21 | # Module manifest path 22 | ModuleManifestPath = $env:BHPSModuleManifest 23 | } 24 | Build = @{ 25 | 26 | # "Dependencies" moved to TaskDependencies section 27 | 28 | # Output directory when building a module 29 | OutDir = $outDir 30 | 31 | # Module output directory 32 | # This will be computed in 'Initialize-PSBuild' so we can allow the user to 33 | # override the top-level 'OutDir' above and compute the full path to the module internally 34 | ModuleOutDir = $null 35 | 36 | # Controls whether to "compile" module into single PSM1 or not 37 | CompileModule = $false 38 | 39 | # List of directories that if CompileModule is $true, will be concatenated into the PSM1 40 | CompileDirectories = @('Enum', 'Classes', 'Private', 'Public') 41 | 42 | # List of directories that will always be copied "as is" to output directory 43 | CopyDirectories = @() 44 | 45 | # List of files (regular expressions) to exclude from output directory 46 | Exclude = @() 47 | } 48 | Test = @{ 49 | # Enable/disable Pester tests 50 | Enabled = $true 51 | 52 | # Directory containing Pester tests 53 | RootDir = [IO.Path]::Combine($env:BHProjectPath, 'tests') 54 | 55 | # Specifies an output file path to send to Invoke-Pester's -OutputFile parameter. 56 | # This is typically used to write out test results so that they can be sent to a CI system 57 | # This path is relative to the directory containing Pester tests 58 | OutputFile = [IO.Path]::Combine($env:BHProjectPath, 'testResults.xml') 59 | 60 | # Specifies the test output format to use when the TestOutputFile property is given 61 | # a path. This parameter is passed through to Invoke-Pester's -OutputFormat parameter. 62 | OutputFormat = 'NUnitXml' 63 | 64 | ScriptAnalysis = @{ 65 | # Enable/disable use of PSScriptAnalyzer to perform script analysis 66 | Enabled = $true 67 | 68 | # When PSScriptAnalyzer is enabled, control which severity level will generate a build failure. 69 | # Valid values are Error, Warning, Information and None. "None" will report errors but will not 70 | # cause a build failure. "Error" will fail the build only on diagnostic records that are of 71 | # severity error. "Warning" will fail the build on Warning and Error diagnostic records. 72 | # "Any" will fail the build on any diagnostic record, regardless of severity. 73 | FailBuildOnSeverityLevel = 'Error' 74 | 75 | # Path to the PSScriptAnalyzer settings file. 76 | SettingsPath = [IO.Path]::Combine($PSScriptRoot, 'ScriptAnalyzerSettings.psd1') 77 | } 78 | 79 | # Import module from OutDir prior to running Pester tests. 80 | ImportModule = $false 81 | 82 | CodeCoverage = @{ 83 | # Enable/disable Pester code coverage reporting. 84 | Enabled = $false 85 | 86 | # Fail Pester code coverage test if below this threshold 87 | Threshold = .75 88 | 89 | # CodeCoverageFiles specifies the files to perform code coverage analysis on. This property 90 | # acts as a direct input to the Pester -CodeCoverage parameter, so will support constructions 91 | # like the ones found here: https://pester.dev/docs/usage/code-coverage. 92 | Files = @() 93 | 94 | # Path to write code coverage report to 95 | OutputFile = [IO.Path]::Combine($env:BHProjectPath, 'codeCoverage.xml') 96 | 97 | # The code coverage output format to use 98 | OutputFileFormat = 'JaCoCo' 99 | } 100 | 101 | # Skip remaining tests after failure for selected scope. Options are None, Run, Container and Block. Default: None. 102 | SkipRemainingOnFailure = 'None' 103 | 104 | # Set verbosity of output. Options are None, Normal, Detailed and Diagnostic. Default: Detailed. 105 | OutputVerbosity = 'Detailed' 106 | } 107 | Help = @{ 108 | # Path to updatable help CAB 109 | UpdatableHelpOutDir = [IO.Path]::Combine($outDir, 'UpdatableHelp') 110 | 111 | # Default Locale used for help generation, defaults to en-US 112 | # Get-UICulture doesn't return a name on Linux so default to en-US 113 | DefaultLocale = if (-not (Get-UICulture).Name) { 'en-US' } else { (Get-UICulture).Name } 114 | 115 | # Convert project readme into the module about file 116 | ConvertReadMeToAboutHelp = $false 117 | } 118 | Docs = @{ 119 | # Directory PlatyPS markdown documentation will be saved to 120 | RootDir = [IO.Path]::Combine($env:BHProjectPath, 'docs') 121 | 122 | # Whether to overwrite existing markdown files and use comment based help as the source of truth 123 | Overwrite = $false 124 | 125 | # Whether to order parameters alphabetically by name in PARAMETERS section. 126 | # Value passed to New-MarkdownHelp and Update-MarkdownHelp. 127 | AlphabeticParamsOrder = $false 128 | 129 | # Exclude the parameters marked with `DontShow` in the parameter attribute from the help content. 130 | # Value passed to New-MarkdownHelp and Update-MarkdownHelp. 131 | ExcludeDontShow = $false 132 | 133 | # Indicates that the target document will use a full type name instead of a short name for parameters. 134 | # Value passed to New-MarkdownHelp and Update-MarkdownHelp. 135 | UseFullTypeName = $false 136 | } 137 | Publish = @{ 138 | # PowerShell repository name to publish modules to 139 | PSRepository = 'PSGallery' 140 | 141 | # API key to authenticate to PowerShell repository with 142 | PSRepositoryApiKey = $env:PSGALLERY_API_KEY 143 | 144 | # Credential to authenticate to PowerShell repository with 145 | PSRepositoryCredential = $null 146 | } 147 | TaskDependencies = @{ 148 | Clean = @('Init') 149 | StageFiles = @('Clean') 150 | Build = @('StageFiles', 'BuildHelp') 151 | Analyze = @('Build') 152 | Pester = @('Build') 153 | Test = @('Pester', 'Analyze') 154 | BuildHelp = @('GenerateMarkdown', 'GenerateMAML') 155 | GenerateMarkdown = @('StageFiles') 156 | GenerateMAML = @('GenerateMarkdown') 157 | GenerateUpdatableHelp = @('BuildHelp') 158 | Publish = @('Test') 159 | } 160 | } 161 | 162 | # Enable/disable generation of a catalog (.cat) file for the module. 163 | # [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] 164 | # $catalogGenerationEnabled = $true 165 | 166 | # # Select the hash version to use for the catalog file: 1 for SHA1 (compat with Windows 7 and 167 | # # Windows Server 2008 R2), 2 for SHA2 to support only newer Windows versions. 168 | # [System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')] 169 | # $catalogVersion = 2 170 | -------------------------------------------------------------------------------- /PowerShellBuild/psakeFile.ps1: -------------------------------------------------------------------------------- 1 | # spell-checker:ignore Reqs 2 | # Load in build settings 3 | Remove-Variable -Name PSBPreference -Scope Script -Force -ErrorAction Ignore 4 | Set-Variable -Name PSBPreference -Option ReadOnly -Scope Script -Value (. ([IO.Path]::Combine($PSScriptRoot, 'build.properties.ps1'))) 5 | 6 | Properties {} 7 | 8 | FormatTaskName { 9 | param($taskName) 10 | Write-Host 'Task: ' -ForegroundColor Cyan -NoNewline 11 | Write-Host $taskName.ToUpper() -ForegroundColor Blue 12 | } 13 | 14 | # This psake file is meant to be referenced from another 15 | # Can't have two 'default' tasks 16 | # Task default -depends Test 17 | 18 | Task Init { 19 | Initialize-PSBuild -UseBuildHelpers -BuildEnvironment $PSBPreference 20 | } -description 'Initialize build environment variables' 21 | 22 | Task Clean -depends $PSBPreference.TaskDependencies.Clean { 23 | Clear-PSBuildOutputFolder -Path $PSBPreference.Build.ModuleOutDir 24 | } -description 'Clears module output directory' 25 | 26 | Task StageFiles -depends $PSBPreference.TaskDependencies.StageFiles { 27 | $buildParams = @{ 28 | Path = $PSBPreference.General.SrcRootDir 29 | ModuleName = $PSBPreference.General.ModuleName 30 | DestinationPath = $PSBPreference.Build.ModuleOutDir 31 | Exclude = $PSBPreference.Build.Exclude 32 | Compile = $PSBPreference.Build.CompileModule 33 | CompileDirectories = $PSBPreference.Build.CompileDirectories 34 | CopyDirectories = $PSBPreference.Build.CopyDirectories 35 | Culture = $PSBPreference.Help.DefaultLocale 36 | } 37 | 38 | if ($PSBPreference.Help.ConvertReadMeToAboutHelp) { 39 | $readMePath = Get-ChildItem -Path $PSBPreference.General.ProjectRoot -Include 'readme.md', 'readme.markdown', 'readme.txt' -Depth 1 | 40 | Select-Object -First 1 41 | if ($readMePath) { 42 | $buildParams.ReadMePath = $readMePath 43 | } 44 | } 45 | 46 | # only add these configuration values to the build parameters if they have been been set 47 | 'CompileHeader', 'CompileFooter', 'CompileScriptHeader', 'CompileScriptFooter' | ForEach-Object { 48 | if ($PSBPreference.Build.Keys -contains $_) { 49 | $buildParams.$_ = $PSBPreference.Build.$_ 50 | } 51 | } 52 | 53 | Build-PSBuildModule @buildParams 54 | } -description 'Builds module based on source directory' 55 | 56 | Task Build -depends $PSBPreference.TaskDependencies.Build -description 'Builds module and generate help documentation' 57 | 58 | $analyzePreReqs = { 59 | $result = $true 60 | if (-not $PSBPreference.Test.ScriptAnalysis.Enabled) { 61 | Write-Warning 'Script analysis is not enabled.' 62 | $result = $false 63 | } 64 | if (-not (Get-Module -Name PSScriptAnalyzer -ListAvailable)) { 65 | Write-Warning 'PSScriptAnalyzer module is not installed' 66 | $result = $false 67 | } 68 | $result 69 | } 70 | Task Analyze -depends $PSBPreference.TaskDependencies.Analyze -precondition $analyzePreReqs { 71 | $analyzeParams = @{ 72 | Path = $PSBPreference.Build.ModuleOutDir 73 | SeverityThreshold = $PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel 74 | SettingsPath = $PSBPreference.Test.ScriptAnalysis.SettingsPath 75 | } 76 | Test-PSBuildScriptAnalysis @analyzeParams 77 | } -description 'Execute PSScriptAnalyzer tests' 78 | 79 | $pesterPreReqs = { 80 | $result = $true 81 | if (-not $PSBPreference.Test.Enabled) { 82 | Write-Warning 'Pester testing is not enabled.' 83 | $result = $false 84 | } 85 | if (-not (Get-Module -Name Pester -ListAvailable)) { 86 | Write-Warning 'Pester module is not installed' 87 | $result = $false 88 | } 89 | if (-not (Test-Path -Path $PSBPreference.Test.RootDir)) { 90 | Write-Warning "Test directory [$($PSBPreference.Test.RootDir)] not found" 91 | $result = $false 92 | } 93 | return $result 94 | } 95 | Task Pester -depends $PSBPreference.TaskDependencies.Pester -precondition $pesterPreReqs { 96 | $pesterParams = @{ 97 | Path = $PSBPreference.Test.RootDir 98 | ModuleName = $PSBPreference.General.ModuleName 99 | ModuleManifest = Join-Path $PSBPreference.Build.ModuleOutDir "$($PSBPreference.General.ModuleName).psd1" 100 | OutputPath = $PSBPreference.Test.OutputFile 101 | OutputFormat = $PSBPreference.Test.OutputFormat 102 | CodeCoverage = $PSBPreference.Test.CodeCoverage.Enabled 103 | CodeCoverageThreshold = $PSBPreference.Test.CodeCoverage.Threshold 104 | CodeCoverageFiles = $PSBPreference.Test.CodeCoverage.Files 105 | CodeCoverageOutputFile = $PSBPreference.Test.CodeCoverage.OutputFile 106 | CodeCoverageOutputFileFormat = $PSBPreference.Test.CodeCoverage.OutputFileFormat 107 | ImportModule = $PSBPreference.Test.ImportModule 108 | SkipRemainingOnFailure = $PSBPreference.Test.SkipRemainingOnFailure 109 | OutputVerbosity = $PSBPreference.Test.OutputVerbosity 110 | } 111 | Test-PSBuildPester @pesterParams 112 | } -description 'Execute Pester tests' 113 | 114 | Task Test -depends $PSBPreference.TaskDependencies.Test { 115 | } -description 'Execute Pester and ScriptAnalyzer tests' 116 | 117 | Task BuildHelp -depends $PSBPreference.TaskDependencies.BuildHelp {} -description 'Builds help documentation' 118 | 119 | $genMarkdownPreReqs = { 120 | $result = $true 121 | if (-not (Get-Module platyPS -ListAvailable)) { 122 | Write-Warning "platyPS module is not installed. Skipping [$($psake.context.currentTaskName)] task." 123 | $result = $false 124 | } 125 | $result 126 | } 127 | Task GenerateMarkdown -depends $PSBPreference.TaskDependencies.GenerateMarkdown -precondition $genMarkdownPreReqs { 128 | $buildMDParams = @{ 129 | ModulePath = $PSBPreference.Build.ModuleOutDir 130 | ModuleName = $PSBPreference.General.ModuleName 131 | DocsPath = $PSBPreference.Docs.RootDir 132 | Locale = $PSBPreference.Help.DefaultLocale 133 | Overwrite = $PSBPreference.Docs.Overwrite 134 | AlphabeticParamsOrder = $PSBPreference.Docs.AlphabeticParamsOrder 135 | ExcludeDontShow = $PSBPreference.Docs.ExcludeDontShow 136 | UseFullTypeName = $PSBPreference.Docs.UseFullTypeName 137 | } 138 | Build-PSBuildMarkdown @buildMDParams 139 | } -description 'Generates PlatyPS markdown files from module help' 140 | 141 | $genHelpFilesPreReqs = { 142 | $result = $true 143 | if (-not (Get-Module platyPS -ListAvailable)) { 144 | Write-Warning "platyPS module is not installed. Skipping [$($psake.context.currentTaskName)] task." 145 | $result = $false 146 | } 147 | $result 148 | } 149 | Task GenerateMAML -depends $PSBPreference.TaskDependencies.GenerateMAML -precondition $genHelpFilesPreReqs { 150 | Build-PSBuildMAMLHelp -Path $PSBPreference.Docs.RootDir -DestinationPath $PSBPreference.Build.ModuleOutDir 151 | } -description 'Generates MAML-based help from PlatyPS markdown files' 152 | 153 | $genUpdatableHelpPreReqs = { 154 | $result = $true 155 | if (-not (Get-Module platyPS -ListAvailable)) { 156 | Write-Warning "platyPS module is not installed. Skipping [$($psake.context.currentTaskName)] task." 157 | $result = $false 158 | } 159 | $result 160 | } 161 | Task GenerateUpdatableHelp -depends $PSBPreference.TaskDependencies.GenerateUpdatableHelp -precondition $genUpdatableHelpPreReqs { 162 | Build-PSBuildUpdatableHelp -DocsPath $PSBPreference.Docs.RootDir -OutputPath $PSBPreference.Help.UpdatableHelpOutDir 163 | } -description 'Create updatable help .cab file based on PlatyPS markdown help' 164 | 165 | Task Publish -depends $PSBPreference.TaskDependencies.Publish { 166 | Assert -conditionToCheck ($PSBPreference.Publish.PSRepositoryApiKey -or $PSBPreference.Publish.PSRepositoryCredential) -failureMessage "API key or credential not defined to authenticate with [$($PSBPreference.Publish.PSRepository)] with." 167 | 168 | $publishParams = @{ 169 | Path = $PSBPreference.Build.ModuleOutDir 170 | Version = $PSBPreference.General.ModuleVersion 171 | Repository = $PSBPreference.Publish.PSRepository 172 | Verbose = $VerbosePreference 173 | } 174 | if ($PSBPreference.Publish.PSRepositoryApiKey) { 175 | $publishParams.ApiKey = $PSBPreference.Publish.PSRepositoryApiKey 176 | } 177 | 178 | if ($PSBPreference.Publish.PSRepositoryCredential) { 179 | $publishParams.Credential = $PSBPreference.Publish.PSRepositoryCredential 180 | } 181 | 182 | Publish-PSBuildModule @publishParams 183 | } -description 'Publish module to the defined PowerShell repository' 184 | 185 | Task ? -description 'Lists the available tasks' { 186 | 'Available tasks:' 187 | $psake.context.Peek().Tasks.Keys | Sort-Object 188 | } 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShellBuild 2 | 3 | | GitHub Actions | PS Gallery | License | 4 | |-------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|--------------------------------------| 5 | | [![GitHub Actions Status][github-actions-badge]][github-actions-build] [![GitHub Actions Status][github-actions-badge-publish]][github-actions-build] | [![PowerShell Gallery][psgallery-badge]][psgallery] | [![License][license-badge]][license] | 6 | 7 | This project aims to provide common [psake](https://github.com/psake/psake) and 8 | [Invoke-Build](https://github.com/nightroman/Invoke-Build) tasks for building, 9 | testing, and publishing PowerShell modules. 10 | 11 | Using these shared tasks reduces the boilerplate scaffolding needed in most 12 | PowerShell module projects and help enforce a consistent module structure. This 13 | consistency ultimately helps the community in building high-quality PowerShell 14 | modules. 15 | 16 | > If using [psake](https://github.com/psake/psake) as your task runner, version 17 | > `4.8.0` or greater is required to make use of shared tasks distributed in 18 | > separate modules. To install psake `4.8.0` you can run: 19 | 20 | ```powershell 21 | Install-Module -Name psake -RequiredVersion 4.8.0 -Repository PSGallery 22 | ``` 23 | 24 | > For [Invoke-Build](https://github.com/nightroman/Invoke-Build), see the 25 | > [how to dot source tasks using PowerShell aliases](https://github.com/nightroman/Invoke-Build/blob/master/Tasks/Import/README.md#example-2-import-from-a-module-with-tasks) 26 | > example. 27 | 28 |

29 | Logo 30 |

31 | 32 | ## Status - Work in progress 33 | 34 | > This project is a **work in progress** and may change significantly before 35 | > reaching stability based on feedback from the community. **Please do not base 36 | > critical processes on this project** until it has been further refined. 37 | 38 | ## Tasks 39 | 40 | **PowerShellBuild** is a PowerShell module that provides helper functions to 41 | handle the common build, test, and release steps typically found in PowerShell 42 | module projects. These steps are exposed as a set of 43 | [psake](https://github.com/psake/psake) tasks found in 44 | [psakeFile.ps1](./PowerShellBuild/psakeFile.ps1) in the root of the module, and 45 | as PowerShell aliases which you can dot source if using 46 | [Invoke-Build](https://github.com/nightroman/Invoke-Build). In psake `v4.8.0`, a 47 | feature was added to reference shared psake tasks distributed within PowerShell 48 | modules. This allows a set of tasks to be versioned, distributed, and called by 49 | other projects. 50 | 51 | ### Primary Tasks 52 | 53 | These primary tasks are the main tasks you'll typically call as part of 54 | PowerShell module development. 55 | 56 | | Name | Dependencies | Description | 57 | |---------|-----------------------|-------------------------------------------------| 58 | | Init | _none_ | Initialize psake and task variables | 59 | | Clean | init | Clean output directory | 60 | | Build | StageFiles, BuildHelp | Clean and build module in output directory | 61 | | Analyze | Build | Run PSScriptAnalyzer tests | 62 | | Pester | Build | Run Pester tests | 63 | | Test | Analyze, Pester | Run combined tests | 64 | | Publish | Test | Publish module to defined PowerShell repository | 65 | 66 | ### Secondary Tasks 67 | 68 | These secondary tasks are called as dependencies from the primary tasks but may 69 | also be called directly. 70 | 71 | | Name | Dependencies | Description | 72 | |-----------------------|--------------------------------|----------------------------------| 73 | | BuildHelp | GenerateMarkdown, GenerateMAML | Build all help files | 74 | | StageFiles | Clean | Build module in output directory | 75 | | GenerateMarkdown | StageFiles | Build markdown-based help | 76 | | GenerateMAML | GenerateMarkdown | Build MAML help | 77 | | GenerateUpdatableHelp | BuildHelp | Build updatable help cab | 78 | 79 | ## Task customization 80 | 81 | The psake and Invoke-Build tasks can be customized by overriding the values 82 | contained in the `$PSBPreference` hashtable. defined in the psake file. These 83 | settings govern if certain tasks are executed or set default paths used to build 84 | and test the module. You can override these in either psake or Invoke-Build to 85 | match your environment. 86 | 87 | | Setting | Default value | Description | 88 | |-------------------------------------------------------------|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------| 89 | | $PSBPreference.General.ProjectRoot | `$env:BHProjectPath` | Root directory for the project | 90 | | $PSBPreference.General.SrcRootDir | `$env:BHPSModulePath` | Root directory for the module | 91 | | $PSBPreference.General.ModuleName | `$env:BHProjectName` | The name of the module. This should match the basename of the PSD1 file | 92 | | $PSBPreference.General.ModuleVersion | `\` | The version of the module | 93 | | $PSBPreference.General.ModuleManifestPath | `$env:BHPSModuleManifest` | Path to the module manifest (PSD1) | 94 | | $PSBPreference.Build.OutDir | `$projectRoot/Output` | Output directory when building the module | 95 | | $PSBPreference.Build.Dependencies | 'StageFiles, 'BuildHelp' | Default task dependencies for the `Build` task | 96 | | $PSBPreference.Build.ModuleOutDir | `$outDir/$moduleName/$moduleVersion` | `For internal use only. Do not overwrite. Use '$PSBPreference.Build.OutDir' to set output directory` | 97 | | $PSBPreference.Build.CompileModule | `$false` | Controls whether to "compile" module into single PSM1 or not | 98 | | $PSBPreference.Build.CompileDirectories | `@('Enum', 'Classes', 'Private', 'Public')` | List of directories to "compile" into monolithic PSM1. Only valid when `$PSBPreference.Build.CompileModule` is `$true`. | 99 | | $PSBPreference.Build.CopyDirectories | `@()` | List of directories to copy "as-is" to built module | 100 | | $PSBPreference.Build.CompileHeader | `` | String that appears at the top of your compiled PSM1 file | 101 | | $PSBPreference.Build.CompileFooter | `` | String that appears at the bottom of your compiled PSM1 file | 102 | | $PSBPreference.Build.CompileScriptHeader | `` | String that appears in your compiled PSM1 file before each added script | 103 | | $PSBPreference.Build.CompileScriptFooter | `` | String that appears in your compiled PSM1 file after each added script | 104 | | $PSBPreference.Build.Exclude | `` | Array of files (regular expressions) to exclude when building module | 105 | | $PSBPreference.Test.Enabled | `$true` | Enable/disable Pester tests | 106 | | $PSBPreference.Test.RootDir | `$projectRoot/tests` | Directory containing Pester tests | 107 | | $PSBPreference.Test.OutputFile | `$null` | Output file path Pester will save test results to | 108 | | $PSBPreference.Test.OutputFormat | `NUnitXml` | Test output format to use when saving Pester test results | 109 | | $PSBPreference.Test.ScriptAnalysis.Enabled | `$true` | Enable/disable use of PSScriptAnalyzer to perform script analysis | 110 | | $PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel | `Error` | PSScriptAnalyzer threshold to fail the build on | 111 | | $PSBPreference.Test.ScriptAnalysis.SettingsPath | `./ScriptAnalyzerSettings.psd1` | Path to the PSScriptAnalyzer settings file | 112 | | $PSBPreference.Test.CodeCoverage.Enabled | `$false` | Enable/disable Pester code coverage reporting | 113 | | $PSBPreference.Test.CodeCoverage.Threshold | `.75` | Fail Pester code coverage test if below this threshold | 114 | | $PSBPreference.Test.CodeCoverage.Files | `*.ps1, *.psm1` | Files to perform code coverage analysis on | 115 | | $PSBPreference.Test.CodeCoverage.OutputFile | `coverage.xml` | Output file path (relative to Pester test directory) where Pester will save code coverage results to | 116 | | $PSBPreference.Test.CodeCoverage.OutputFileFormat | `$null` | Test output format to use when saving Pester code coverage results | 117 | | $PSBPreference.Test.ImportModule | `$false` | Import module from output directory prior to running Pester tests | 118 | | $PSBPreference.Test.SkipRemainingOnFailure | `None` | Skip remaining tests after failure for selected scope. Options are None, Run, Container and Block. | 119 | | $PSBPreference.Test.OutputVerbosity | `Detailed` | Set verbosity of output. Options are None, Normal, Detailed and Diagnostic. | 120 | | $PSBPreference.Help.UpdatableHelpOutDir | `$OutDir/UpdatableHelp` | Output directory to store update module help (CAB) | 121 | | $PSBPreference.Help.DefaultLocale | `(Get-UICulture).Name` | Default locale used for help generation | 122 | | $PSBPreference.Help.ConvertReadMeToAboutHelp | `$false` | Convert project readme into the module about file | 123 | | $PSBPreference.Docs.RootDir | `$projectRoot/docs` | Directory PlatyPS markdown documentation will be saved to | 124 | | $PSBPreference.Docs.Overwrite | `$false` | Overwrite the markdown files in the docs folder using the comment based help as the source of truth. | 125 | | $PSBPreference.Docs.AlphabeticParamsOrder | `$false` | Order parameters alphabetically by name in PARAMETERS section. There are 5 exceptions: -Confirm, -WhatIf, -IncludeTotalCount, -Skip, and -First parameters will be the last. | 126 | | $PSBPreference.Docs.ExcludeDontShow | `$false` | Exclude the parameters marked with `DontShow` in the parameter attribute from the help content. | 127 | | $PSBPreference.Docs.UseFullTypeName | `$false` | Indicates that the target document will use a full type name instead of a short name for parameters. | 128 | | $PSBPreference.Publish.PSRepository | `PSGallery` | PowerShell repository name to publish | 129 | | $PSBPreference.Publish.PSRepositoryApiKey | `$env:PSGALLERY_API_KEY` | API key to authenticate to PowerShell repository with | 130 | | $PSBPreference.Publish.PSRepositoryCredential | `$null` | Credential to authenticate to PowerShell repository with. Overrides `$psRepositoryApiKey` if defined | 131 | | $PSBPreference.TaskDependencies.Clean | 'Init' | Tasks the 'Clean' task depends on. | 132 | | $PSBPreference.TaskDependencies.StageFiles | 'Clean' | Tasks the 'StageFiles' task depends on. | 133 | | $PSBPreference.TaskDependencies.Build | 'StageFiles', 'BuildHelp' | Tasks the 'Build' task depends on. | 134 | | $PSBPreference.TaskDependencies.Analyze | 'Build' | Tasks the 'Analyze' task depends on. | 135 | | $PSBPreference.TaskDependencies.Pester | 'Build' | Tasks the 'Pester' task depends on. | 136 | | $PSBPreference.TaskDependencies.Test | 'Pester', 'Analyze' | Tasks the 'Test' task depends on. | 137 | | $PSBPreference.TaskDependencies.BuildHelp | 'GenerateMarkdown', 'GenerateMAML' | Tasks the 'BuildHelp' task depends on. | 138 | | $PSBPreference.TaskDependencies.GenerateMarkdown | 'StageFiles' | Tasks the 'GenerateMarkdown' task depends on. | 139 | | $PSBPreference.TaskDependencies.GenerateMAML | 'GenerateMarkdown' | Tasks the 'GenerateMAML' task depends on. | 140 | | $PSBPreference.TaskDependencies.GenerateUpdatableHelp | 'BuildHelp' | Tasks the 'GenerateUpdatableHelp' task depends on. | 141 | | $PSBPreference.TaskDependencies.Publish | 'Test' | Tasks the 'Publish' task depends on. | 142 | 143 | ## Examples 144 | 145 | ### psake 146 | 147 | The example below is a psake file you might use in your PowerShell module. When 148 | psake executes this file, it will recognize that tasks are being referenced from 149 | a separate module and automatically load them. You can run these tasks just as 150 | if they were included directly in your task file. 151 | 152 | Notice that the task file contained in `MyModule` only references the `Build` 153 | task supplied from `PowerShellBuild`. When executed, the dependent tasks `Init`, 154 | `Clear`, and `StageFiles` also contained in `PowerShellBuild` are executed as 155 | well. 156 | 157 | #### psakeBuild.ps1 158 | 159 | ```powershell 160 | properties { 161 | # These settings overwrite values supplied from the PowerShellBuild 162 | # module and govern how those tasks are executed 163 | $PSBPreference.Test.ScriptAnalysisEnabled = $false 164 | $PSBPreference.Test.CodeCoverage.Enabled = $true 165 | } 166 | 167 | task default -depends Build 168 | 169 | task Build -FromModule PowerShellBuild -Version '0.1.0' 170 | ``` 171 | 172 | ![Example](./media/psake_example.png) 173 | 174 | ### Invoke-Build 175 | 176 | The example below is an 177 | [Invoke-Build](https://github.com/nightroman/Invoke-Build) task file that 178 | imports the `PowerShellBuild` module which contains the shared tasks and then 179 | dot sources the Invoke-Build task files that are referenced by the PowerShell 180 | alias `PowerShellBuild.IB.Tasks`. Additionally, certain settings that control 181 | how the build tasks operate are overwritten after the tasks have been imported. 182 | 183 | #### .build.ps1 184 | 185 | ```powershell 186 | Import-Module PowerShellBuild 187 | . PowerShellBuild.IB.Tasks 188 | 189 | # Overwrite build settings contained in PowerShellBuild 190 | $PSBPreference.Test.ScriptAnalysisEnabled = $true 191 | $PSBPreference.Test.CodeCoverage.Enabled = $false 192 | ``` 193 | 194 | ![Example](./media/ib_example.png) 195 | 196 | [github-actions-badge]: https://github.com/psake/PowerShellBuild/actions/workflows/test.yml/badge.svg 197 | [github-actions-badge-publish]: https://github.com/psake/PowerShellBuild/actions/workflows/publish.yaml/badge.svg 198 | [github-actions-build]: https://github.com/psake/PowerShellBuild/actions 199 | [psgallery-badge]: https://img.shields.io/powershellgallery/dt/powershellbuild.svg 200 | [psgallery]: https://www.powershellgallery.com/packages/PowerShellBuild 201 | [license-badge]: https://img.shields.io/github/license/psake/PowerShellBuild.svg 202 | [license]: https://raw.githubusercontent.com/psake/PowerShellBuild/main/LICENSE 203 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding(DefaultParameterSetName = 'Task')] 2 | param( 3 | # Build task(s) to execute 4 | [parameter(ParameterSetName = 'task', position = 0)] 5 | [ArgumentCompleter( { 6 | param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) 7 | $psakeFile = './psakeFile.ps1' 8 | switch ($Parameter) { 9 | 'Task' { 10 | if ([string]::IsNullOrEmpty($WordToComplete)) { 11 | Get-PSakeScriptTasks -buildFile $psakeFile | Select-Object -ExpandProperty Name 12 | } else { 13 | Get-PSakeScriptTasks -buildFile $psakeFile | 14 | Where-Object { $_.Name -match $WordToComplete } | 15 | Select-Object -ExpandProperty Name 16 | } 17 | } 18 | default { 19 | } 20 | } 21 | })] 22 | [string[]]$Task = 'default', 23 | 24 | # Bootstrap dependencies 25 | [switch]$Bootstrap, 26 | 27 | # List available build tasks 28 | [parameter(ParameterSetName = 'Help')] 29 | [switch]$Help, 30 | 31 | [pscredential]$PSGalleryApiKey 32 | ) 33 | 34 | $ErrorActionPreference = 'Stop' 35 | 36 | # Bootstrap dependencies 37 | if ($Bootstrap.IsPresent) { 38 | PackageManagement\Get-PackageProvider -Name Nuget -ForceBootstrap | Out-Null 39 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted 40 | if (-not (Get-Module -Name PSDepend -ListAvailable)) { 41 | Install-Module -Name PSDepend -Repository PSGallery 42 | } 43 | Import-Module -Name PSDepend -Verbose:$false 44 | Invoke-PSDepend -Path './requirements.psd1' -Install -Import -Force -WarningAction SilentlyContinue 45 | } 46 | 47 | # Execute psake task(s) 48 | $psakeFile = './psakeFile.ps1' 49 | if ($PSCmdlet.ParameterSetName -eq 'Help') { 50 | Get-PSakeScriptTasks -buildFile $psakeFile | 51 | Format-Table -Property Name, Description, Alias, DependsOn 52 | } else { 53 | Set-BuildEnvironment -Force 54 | $parameters = @{} 55 | if ($PSGalleryApiKey) { 56 | $parameters['galleryApiKey'] = $PSGalleryApiKey 57 | } 58 | Invoke-psake -buildFile $psakeFile -taskList $Task -nologo -parameters $parameters 59 | exit ( [int]( -not $psake.build_success ) ) 60 | } 61 | -------------------------------------------------------------------------------- /build.settings.ps1: -------------------------------------------------------------------------------- 1 | $projectRoot = if ($ENV:BHProjectPath) { $ENV:BHProjectPath } else { $PSScriptRoot } 2 | $moduleName = $env:BHProjectName 3 | $moduleVersion = (Import-PowerShellDataFile -Path $env:BHPSModuleManifest).ModuleVersion 4 | $outDir = [IO.Path]::Combine($projectRoot, 'Output') 5 | $moduleOutDir = "$outDir/$moduleName/$moduleVersion" 6 | @{ 7 | ProjectRoot = $projectRoot 8 | ProjectName = $env:BHProjectName 9 | SUT = $env:BHModulePath 10 | Tests = Get-ChildItem -Path ([IO.Path]::Combine($projectRoot, 'tests')) -Filter '*.tests.ps1' 11 | OutputDir = $outDir 12 | ModuleOutDir = $moduleOutDir 13 | ManifestPath = $env:BHPSModuleManifest 14 | Manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest 15 | PSVersion = $PSVersionTable.PSVersion.ToString() 16 | PSGalleryApiKey = $env:PSGALLERY_API_KEY 17 | } 18 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "ignorePaths": [], 4 | "dictionaryDefinitions": [], 5 | "dictionaries": [ 6 | "powershell", 7 | "csharp", 8 | "json", 9 | "xml", 10 | "markdown" 11 | ], 12 | "words": [], 13 | "ignoreWords": [ 14 | "psake", 15 | "MAML" 16 | ], 17 | "import": [] 18 | } 19 | -------------------------------------------------------------------------------- /media/ib_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psake/PowerShellBuild/63340deec03da255d163a17f30647fdd04cbe2c1/media/ib_example.png -------------------------------------------------------------------------------- /media/psake_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psake/PowerShellBuild/63340deec03da255d163a17f30647fdd04cbe2c1/media/psake_example.png -------------------------------------------------------------------------------- /media/psaketaskmodule-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psake/PowerShellBuild/63340deec03da255d163a17f30647fdd04cbe2c1/media/psaketaskmodule-256x256.png -------------------------------------------------------------------------------- /psakeFile.ps1: -------------------------------------------------------------------------------- 1 | properties { 2 | $settings = . ([IO.Path]::Combine($PSScriptRoot, 'build.settings.ps1')) 3 | if ($galleryApiKey) { 4 | $settings.PSGalleryApiKey = $galleryApiKey.GetNetworkCredential().password 5 | } 6 | } 7 | 8 | task default -depends Test 9 | 10 | task Init { 11 | "STATUS: Testing with PowerShell $($settings.PSVersion)" 12 | 'Build System Details:' 13 | Get-Item ENV:BH* 14 | } -description 'Initialize build environment' 15 | 16 | task Test -Depends Init, Analyze, Pester -description 'Run test suite' 17 | 18 | task Analyze -depends Build { 19 | $analysis = Invoke-ScriptAnalyzer -Path $settings.ModuleOutDir -Recurse -Verbose:$false -Settings ([IO.Path]::Combine($env:BHModulePath, 'ScriptAnalyzerSettings.psd1')) 20 | $errors = $analysis | Where-Object {$_.Severity -eq 'Error'} 21 | $warnings = $analysis | Where-Object {$_.Severity -eq 'Warning'} 22 | if (@($errors).Count -gt 0) { 23 | Write-Error -Message 'One or more Script Analyzer errors were found. Build cannot continue!' 24 | $errors | Format-Table -AutoSize 25 | } 26 | 27 | if (@($warnings).Count -gt 0) { 28 | Write-Warning -Message 'One or more Script Analyzer warnings were found. These should be corrected.' 29 | $warnings | Format-Table -AutoSize 30 | } 31 | } -description 'Run PSScriptAnalyzer' 32 | 33 | task Pester -depends Build { 34 | Remove-Module $settings.ProjectName -ErrorAction SilentlyContinue -Verbose:$false 35 | 36 | $testResultsXml = [IO.Path]::Combine($settings.OutputDir, 'testResults.xml') 37 | $testResults = Invoke-Pester -Path $settings.Tests -Output Detailed -PassThru 38 | 39 | if ($testResults.FailedCount -gt 0) { 40 | $testResults | Format-List 41 | Write-Error -Message 'One or more Pester tests failed. Build cannot continue!' 42 | } 43 | } -description 'Run Pester tests' 44 | 45 | task Clean -depends Init { 46 | if (Test-Path -Path $settings.ModuleOutDir) { 47 | Remove-Item -Path $settings.ModuleOutDir -Recurse -Force -Verbose:$false 48 | } 49 | } 50 | 51 | task Build -depends Init, Clean { 52 | New-Item -Path $settings.ModuleOutDir -ItemType Directory -Force > $null 53 | Copy-Item -Path "$($settings.SUT)/*" -Destination $settings.ModuleOutDir -Recurse 54 | 55 | # Commented out rather than removed to allow easy use in future 56 | # Generate Invoke-Build tasks from Psake tasks 57 | # $psakePath = [IO.Path]::Combine($settings.ModuleOutDir, 'psakefile.ps1') 58 | # $ibPath = [IO.Path]::Combine($settings.ModuleOutDir, 'IB.tasks.ps1') 59 | # & .\Build\Convert-PSAke.ps1 $psakePath | Out-File -Encoding UTF8 $ibPath 60 | } 61 | 62 | task Publish -depends Test { 63 | " Publishing version [$($settings.Manifest.ModuleVersion)] to PSGallery..." 64 | if ($settings.PSGalleryApiKey) { 65 | Publish-Module -Path $settings.ModuleOutDir -NuGetApiKey $settings.PSGalleryApiKey 66 | } else { 67 | throw 'Did not find PSGallery API key!' 68 | } 69 | } -description 'Publish to PowerShellGallery' 70 | -------------------------------------------------------------------------------- /requirements.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | PSDependOptions = @{ 3 | Target = 'CurrentUser' 4 | } 5 | BuildHelpers = '2.0.16' 6 | Pester = @{ 7 | MinimumVersion = '5.6.1' 8 | Parameters = @{ 9 | SkipPublisherCheck = $true 10 | } 11 | } 12 | psake = '4.9.0' 13 | PSScriptAnalyzer = '1.24.0' 14 | InvokeBuild = '5.8.1' 15 | platyPS = '0.14.2' 16 | } 17 | -------------------------------------------------------------------------------- /tests/Help.tests.ps1: -------------------------------------------------------------------------------- 1 | # Taken with love from @juneb_get_help (https://raw.githubusercontent.com/juneb/PesterTDD/master/Module.Help.Tests.ps1) 2 | 3 | BeforeDiscovery { 4 | function global:FilterOutCommonParams { 5 | param ($Params) 6 | $commonParams = [System.Management.Automation.PSCmdlet]::OptionalCommonParameters + 7 | [System.Management.Automation.PSCmdlet]::CommonParameters 8 | $params | Where-Object { $_.Name -notin $commonParams } | Sort-Object -Property Name -Unique 9 | } 10 | 11 | $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest 12 | $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' 13 | $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName 14 | $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion 15 | $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" 16 | 17 | # Get module commands 18 | # Remove all versions of the module from the session. Pester can't handle multiple versions. 19 | Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore 20 | Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop 21 | $params = @{ 22 | Module = (Get-Module $env:BHProjectName) 23 | CommandType = [System.Management.Automation.CommandTypes[]]'Cmdlet, Function' # Not alias 24 | } 25 | if ($PSVersionTable.PSVersion.Major -lt 6) { 26 | $params.CommandType[0] += 'Workflow' 27 | } 28 | $commands = Get-Command @params 29 | 30 | ## When testing help, remember that help is cached at the beginning of each session. 31 | ## To test, restart session. 32 | } 33 | 34 | AfterAll { 35 | Remove-Item Function:/FilterOutCommonParams 36 | } 37 | 38 | Describe "Test help for <_.Name>" -ForEach $commands { 39 | 40 | BeforeDiscovery { 41 | # Get command help, parameters, and links 42 | $command = $_ 43 | $commandHelp = Get-Help $command.Name -ErrorAction SilentlyContinue 44 | $commandParameters = global:FilterOutCommonParams -Params $command.ParameterSets.Parameters 45 | $commandParameterNames = $commandParameters.Name 46 | $helpLinks = $commandHelp.relatedLinks.navigationLink.uri 47 | } 48 | 49 | BeforeAll { 50 | # These vars are needed in both discovery and test phases so we need to duplicate them here 51 | $command = $_ 52 | $commandName = $_.Name 53 | $commandHelp = Get-Help $command.Name -ErrorAction SilentlyContinue 54 | $commandParameters = global:FilterOutCommonParams -Params $command.ParameterSets.Parameters 55 | $commandParameterNames = $commandParameters.Name 56 | $helpParameters = global:FilterOutCommonParams -Params $commandHelp.Parameters.Parameter 57 | $helpParameterNames = $helpParameters.Name 58 | } 59 | 60 | # If help is not found, synopsis in auto-generated help is the syntax diagram 61 | It 'Help is not auto-generated' { 62 | $commandHelp.Synopsis | Should -Not -BeLike '*`[``]*' 63 | } 64 | 65 | # Should be a description for every function 66 | It "Has description" { 67 | $commandHelp.Description | Should -Not -BeNullOrEmpty 68 | } 69 | 70 | # Should be at least one example 71 | It "Has example code" { 72 | ($commandHelp.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty 73 | } 74 | 75 | # Should be at least one example description 76 | It "Has example help" { 77 | ($commandHelp.Examples.Example.Remarks | Select-Object -First 1).Text | Should -Not -BeNullOrEmpty 78 | } 79 | 80 | It "Help link <_> is valid" -ForEach $helpLinks { 81 | (Invoke-WebRequest -Uri $_ -UseBasicParsing).StatusCode | Should -Be '200' 82 | } 83 | 84 | Context "Parameter <_.Name>" -Foreach $commandParameters { 85 | 86 | BeforeAll { 87 | $parameter = $_ 88 | $parameterName = $parameter.Name 89 | $parameterHelp = $commandHelp.parameters.parameter | Where-Object Name -eq $parameterName 90 | $parameterHelpType = if ($parameterHelp.ParameterValue) { $parameterHelp.ParameterValue.Trim() } 91 | } 92 | 93 | # Should be a description for every parameter 94 | It "Has description" { 95 | $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty 96 | } 97 | 98 | # Required value in Help should match IsMandatory property of parameter 99 | It "Has correct [mandatory] value" { 100 | $codeMandatory = $_.IsMandatory.toString() 101 | $parameterHelp.Required | Should -Be $codeMandatory 102 | } 103 | 104 | # Parameter type in help should match code 105 | It "Has correct parameter type" { 106 | $parameterHelpType | Should -Be $parameter.ParameterType.Name 107 | } 108 | } 109 | 110 | Context "Test <_> help parameter help for " -Foreach $helpParameterNames { 111 | 112 | # Shouldn't find extra parameters in help. 113 | It "finds help parameter in code: <_>" { 114 | $_ -in $parameterNames | Should -Be $true 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/IBTasks.tests.ps1: -------------------------------------------------------------------------------- 1 | #requires -module InvokeBuild,Psake 2 | 3 | Describe 'Invoke-Build Tasks' { 4 | BeforeAll { 5 | $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest 6 | $outputDir = [IO.Path]::Combine($ENV:BHProjectPath, 'Output') 7 | $outputModDir = [IO.Path]::Combine($outputDir, $env:BHProjectName) 8 | $outputModVerDir = [IO.Path]::Combine($outputModDir, $manifest.ModuleVersion) 9 | $ibTasksFilePath = [IO.Path]::Combine($outputModVerDir, 'IB.tasks.ps1') 10 | $psakeFilePath = [IO.Path]::Combine($outputModVerDir, 'psakeFile.ps1') 11 | } 12 | 13 | $IBTasksResult = $null 14 | It 'IB.tasks.ps1 exists' { 15 | Test-Path $IBTasksFilePath | Should -Be $true 16 | } 17 | 18 | It 'Parseable by invoke-build' { 19 | # Run IB in job to not pollute the environment 20 | # Invoke-Build whatif still outputs in Appveyor in Pester even when directed to out-null. This doesn't happen locally. Redirecting all output to null 21 | $IBTasksResult = Start-Job -ScriptBlock { 22 | Invoke-Build -File $using:IBTasksFilePath -Whatif -Result IBTasksResult -ErrorAction Stop *>$null 23 | $IBTasksResult 24 | } | Wait-Job | Receive-Job 25 | 26 | $IBTasksResult | Should -Not -BeNullOrEmpty 27 | } 28 | It 'Contains all the tasks that were in the Psake file' { 29 | # Run psake in job to not pollute the environment 30 | $psakeTaskNames = Start-Job -ScriptBlock { 31 | Invoke-PSake -docs -buildfile $using:psakeFilePath | Where-Object name -notmatch '^(default|\?)$' | ForEach-Object name 32 | } | Wait-Job | Receive-Job 33 | 34 | $IBTaskNames = $IBTasksResult.all.name 35 | foreach ($taskItem in $psakeTaskNames) { 36 | if ($taskitem -notin $IBTaskNames) { 37 | throw "Task $taskitem was not successfully converted by Convert-PSAke" 38 | } 39 | } 40 | $Psaketasknames | Should -Not -BeNullOrEmpty 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Manifest.tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeAll { 2 | Set-BuildEnvironment -Force 3 | $moduleName = $env:BHProjectName 4 | $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest 5 | $outputDir = Join-Path -Path $ENV:BHProjectPath -ChildPath 'Output' 6 | $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName 7 | $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion 8 | $outputManifestPath = Join-Path -Path $outputModVerDir -Child "$($moduleName).psd1" 9 | $manifestData = Test-ModuleManifest -Path $outputManifestPath -Verbose:$false -ErrorAction Stop -WarningAction SilentlyContinue 10 | 11 | $changelogPath = Join-Path -Path $env:BHProjectPath -Child 'CHANGELOG.md' 12 | $changelogVersion = Get-Content $changelogPath | ForEach-Object { 13 | if ($_ -match "^##\s\[(?(\d+\.){1,3}\d+)\]") { 14 | $changelogVersion = $matches.Version 15 | break 16 | } 17 | } 18 | 19 | $script:manifest = $null 20 | } 21 | Describe 'Module manifest' { 22 | 23 | Context 'Validation' { 24 | 25 | It 'Has a valid manifest' { 26 | $manifestData | Should -Not -BeNullOrEmpty 27 | } 28 | 29 | It 'Has a valid name in the manifest' { 30 | $manifestData.Name | Should -Be $moduleName 31 | } 32 | 33 | It 'Has a valid root module' { 34 | $manifestData.RootModule | Should -Be "$($moduleName).psm1" 35 | } 36 | 37 | It 'Has a valid version in the manifest' { 38 | $manifestData.Version -as [Version] | Should -Not -BeNullOrEmpty 39 | } 40 | 41 | It 'Has a valid description' { 42 | $manifestData.Description | Should -Not -BeNullOrEmpty 43 | } 44 | 45 | It 'Has a valid author' { 46 | $manifestData.Author | Should -Not -BeNullOrEmpty 47 | } 48 | 49 | It 'Has a valid guid' { 50 | {[guid]::Parse($manifestData.Guid)} | Should -Not -Throw 51 | } 52 | 53 | It 'Has a valid copyright' { 54 | $manifestData.CopyRight | Should -Not -BeNullOrEmpty 55 | } 56 | 57 | It 'Has a valid version in the changelog' { 58 | $changelogVersion | Should -Not -BeNullOrEmpty 59 | $changelogVersion -as [Version] | Should -Not -BeNullOrEmpty 60 | } 61 | 62 | It 'Changelog and manifest versions are the same' { 63 | $changelogVersion -as [Version] | Should -Be ( $manifestData.Version -as [Version] ) 64 | } 65 | } 66 | } 67 | 68 | Describe 'Git tagging' -Skip { 69 | BeforeAll { 70 | $gitTagVersion = $null 71 | 72 | if ($git = Get-Command git -CommandType Application -ErrorAction SilentlyContinue) { 73 | $thisCommit = & $git log --decorate --oneline HEAD~1..HEAD 74 | if ($thisCommit -match 'tag:\s*(\d+(?:\.\d+)*)') { $gitTagVersion = $matches[1] } 75 | } 76 | } 77 | 78 | It 'Is tagged with a valid version' { 79 | $gitTagVersion | Should -Not -BeNullOrEmpty 80 | $gitTagVersion -as [Version] | Should -Not -BeNullOrEmpty 81 | } 82 | 83 | It 'Matches manifest version' { 84 | $manifestData.Version -as [Version] | Should -Be ( $gitTagVersion -as [Version]) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/Meta.tests.ps1: -------------------------------------------------------------------------------- 1 | BeforeAll { 2 | 3 | Set-StrictMode -Version latest 4 | 5 | # Make sure MetaFixers.psm1 is loaded - it contains Get-TextFilesList 6 | Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'MetaFixers.psm1') -Verbose:$false -Force 7 | 8 | $projectRoot = $ENV:BHProjectPath 9 | if (-not $projectRoot) { 10 | $projectRoot = $PSScriptRoot 11 | } 12 | 13 | $allTextFiles = Get-TextFilesList $projectRoot 14 | $unicodeFilesCount = 0 15 | $totalTabsCount = 0 16 | foreach ($textFile in $allTextFiles) { 17 | if (Test-FileUnicode $textFile) { 18 | $unicodeFilesCount++ 19 | Write-Warning ( 20 | "File $($textFile.FullName) contains 0x00 bytes." + 21 | " It probably uses Unicode/UTF-16 and needs to be converted to UTF-8." + 22 | " Use Fixer 'Get-UnicodeFilesList `$pwd | ConvertTo-UTF8'." 23 | ) 24 | } 25 | $unicodeFilesCount | Should -Be 0 26 | 27 | $fileName = $textFile.FullName 28 | (Get-Content $fileName -Raw) | Select-String "`t" | Foreach-Object { 29 | Write-Warning ( 30 | "There are tabs in $fileName." + 31 | " Use Fixer 'Get-TextFilesList `$pwd | ConvertTo-SpaceIndentation'." 32 | ) 33 | $totalTabsCount++ 34 | } 35 | } 36 | } 37 | 38 | Describe 'Text files formatting' { 39 | Context 'File encoding' { 40 | It "No text file uses Unicode/UTF-16 encoding" { 41 | $unicodeFilesCount | Should -Be 0 42 | } 43 | } 44 | 45 | Context 'Indentations' { 46 | It "No text file use tabs for indentations" { 47 | $totalTabsCount | Should -Be 0 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/MetaFixers.psm1: -------------------------------------------------------------------------------- 1 | # Taken with love from https://github.com/PowerShell/DscResource.Tests/blob/master/MetaFixers.psm1 2 | 3 | <# 4 | This module helps fix problems, found by Meta.Tests.ps1 5 | #> 6 | 7 | $ErrorActionPreference = 'stop' 8 | Set-StrictMode -Version latest 9 | 10 | function ConvertTo-UTF8() { 11 | [CmdletBinding()] 12 | [OutputType([void])] 13 | param( 14 | [Parameter(Mandatory, ValueFromPipeline)] 15 | [System.IO.FileInfo]$FileInfo 16 | ) 17 | 18 | process { 19 | $content = Get-Content -Raw -Encoding Unicode -Path $FileInfo.FullName 20 | [System.IO.File]::WriteAllText($FileInfo.FullName, $content, [System.Text.Encoding]::UTF8) 21 | } 22 | } 23 | 24 | function ConvertTo-SpaceIndentation() { 25 | [CmdletBinding()] 26 | [OutputType([void])] 27 | param( 28 | [Parameter(Mandatory, ValueFromPipeline)] 29 | [IO.FileInfo]$FileInfo 30 | ) 31 | 32 | process { 33 | $content = (Get-Content -Raw -Path $FileInfo.FullName) -replace "`t", ' ' 34 | [IO.File]::WriteAllText($FileInfo.FullName, $content) 35 | } 36 | } 37 | 38 | function Get-TextFilesList { 39 | [CmdletBinding()] 40 | [OutputType([IO.FileInfo])] 41 | param( 42 | [Parameter(Mandatory, ValueFromPipeline)] 43 | [string]$Root 44 | ) 45 | 46 | begin { 47 | $txtFileExtentions = @('.gitignore', '.gitattributes', '.ps1', '.psm1', '.psd1', '.json', '.xml', '.cmd', '.mof') 48 | } 49 | 50 | process { 51 | Get-ChildItem -Path $Root -File -Recurse | 52 | Where-Object { $_.Extension -in $txtFileExtentions } 53 | } 54 | } 55 | 56 | function Test-FileUnicode { 57 | [CmdletBinding()] 58 | [OutputType([bool])] 59 | param( 60 | [Parameter(Mandatory, ValueFromPipeline)] 61 | [IO.FileInfo]$FileInfo 62 | ) 63 | 64 | process { 65 | $bytes = [IO.File]::ReadAllBytes($FileInfo.FullName) 66 | $zeroBytes = @($bytes -eq 0) 67 | return [bool]$zeroBytes.Length 68 | } 69 | } 70 | 71 | function Get-UnicodeFilesList() { 72 | [CmdletBinding()] 73 | [OutputType([IO.FileInfo])] 74 | param( 75 | [Parameter(Mandatory)] 76 | [string]$Root 77 | ) 78 | 79 | $root | Get-TextFilesList | Where-Object { Test-FileUnicode $_ } 80 | } 81 | -------------------------------------------------------------------------------- /tests/ScriptAnalyzerSettings.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | 3 | } 4 | -------------------------------------------------------------------------------- /tests/TestModule/.build.ps1: -------------------------------------------------------------------------------- 1 | Import-Module ../../Output/PowerShellBuild -Force 2 | . PowerShellBuild.IB.Tasks 3 | 4 | $PSBPreference.Build.CompileModule = $true 5 | 6 | Task Build $PSBPreference.TaskDependencies.Build 7 | 8 | Task . Build 9 | -------------------------------------------------------------------------------- /tests/TestModule/.gitattributes: -------------------------------------------------------------------------------- 1 | * -crlf 2 | -------------------------------------------------------------------------------- /tests/TestModule/.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Contributions to TestModule are highly encouraged and desired. 4 | Below are some guidelines that will help make the process as smooth as possible. 5 | 6 | ## Getting Started 7 | 8 | - Make sure you have a [GitHub account](https://github.com/signup/free) 9 | - Submit a new issue, assuming one does not already exist. 10 | - Clearly describe the issue including steps to reproduce when it is a bug. 11 | - Make sure you fill in the earliest version that you know has the issue. 12 | - Fork the repository on GitHub 13 | 14 | ## Suggesting Enhancements 15 | 16 | I want to know what you think is missing from TestModule and how it can be made better. 17 | 18 | - When submitting an issue for an enhancement, please be as clear as possible about why you think the enhancement is needed and what the benefit of it would be. 19 | 20 | ## Making Changes 21 | 22 | - From your fork of the repository, create a topic branch where work on your change will take place. 23 | - To quickly create a topic branch based on master; `git checkout -b my_contribution master`. 24 | Please avoid working directly on the `master` branch. 25 | - Make commits of logical units. 26 | - Check for unnecessary whitespace with `git diff --check` before committing. 27 | - Please follow the prevailing code conventions in the repository. 28 | Differences in style make the code harder to understand for everyone. 29 | - Make sure your commit messages are in the proper format. 30 | 31 | ``` 32 | Add more cowbell to Get-Something.ps1 33 | 34 | The functionality of Get-Something would be greatly improved if there was a little 35 | more 'pizzazz' added to it. I propose a cowbell. Adding more cowbell has been 36 | shown in studies to both increase one's mojo, and cement one's status 37 | as a rock legend. 38 | ``` 39 | 40 | - Make sure you have added all the necessary Pester tests for your changes. 41 | - Run _all_ Pester tests in the module to assure nothing else was accidentally broken. 42 | 43 | ## Documentation 44 | 45 | I am infallible and as such my documenation needs no corectoin. 46 | In the highly unlikely event that that is _not_ the case, commits to update or add documentation are highly apprecaited. 47 | 48 | ## Submitting Changes 49 | 50 | - Push your changes to a topic branch in your fork of the repository. 51 | - Submit a pull request to the main repository. 52 | - Once the pull request has been reviewed and accepted, it will be merged with the master branch. 53 | - Celebrate 54 | 55 | ## Additional Resources 56 | 57 | - [General GitHub documentation](https://help.github.com/) 58 | - [GitHub forking documentation](https://guides.github.com/activities/forking/) 59 | - [GitHub pull request documentation](https://help.github.com/send-pull-requests/) 60 | - [GitHub Flow guide](https://guides.github.com/introduction/flow/) 61 | - [GitHub's guide to contributing to open source projects](https://guides.github.com/activities/contributing-to-open-source/) 62 | 63 | -------------------------------------------------------------------------------- /tests/TestModule/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | 27 | ## Your Environment 28 | 29 | * Module version used: 30 | * Operating System and PowerShell version: 31 | 32 | -------------------------------------------------------------------------------- /tests/TestModule/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Related Issue 7 | 8 | 9 | 10 | 11 | 12 | ## Motivation and Context 13 | 14 | 15 | ## How Has This Been Tested? 16 | 17 | 18 | 19 | 20 | ## Screenshots (if appropriate): 21 | 22 | ## Types of changes 23 | 24 | - [ ] Bug fix (non-breaking change which fixes an issue) 25 | - [ ] New feature (non-breaking change which adds functionality) 26 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 27 | 28 | ## Checklist: 29 | 30 | 31 | - [ ] My code follows the code style of this project. 32 | - [ ] My change requires a change to the documentation. 33 | - [ ] I have updated the documentation accordingly. 34 | - [ ] I have read the **CONTRIBUTING** document. 35 | - [ ] I have added tests to cover my changes. 36 | - [ ] All new and existing tests passed. 37 | 38 | -------------------------------------------------------------------------------- /tests/TestModule/.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check in the MyOutput dir 2 | docs/ 3 | Output/ 4 | Tests/*.xml 5 | codeCoverage.xml 6 | testResults.xml 7 | -------------------------------------------------------------------------------- /tests/TestModule/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.PowerShell", 6 | "DavidAnson.vscode-markdownlint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tests/TestModule/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.trimTrailingWhitespace": true, 3 | "files.insertFinalNewline": true, 4 | "editor.insertSpaces": true, 5 | "editor.tabSize": 4, 6 | "powershell.codeFormatting.preset": "OTBS" 7 | } 8 | -------------------------------------------------------------------------------- /tests/TestModule/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | 6 | // Start PowerShell (pwsh on *nix) 7 | "windows": { 8 | "options": { 9 | "shell": { 10 | "executable": "powershell.exe", 11 | "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command" ] 12 | } 13 | } 14 | }, 15 | "linux": { 16 | "options": { 17 | "shell": { 18 | "executable": "/usr/bin/pwsh", 19 | "args": [ "-NoProfile", "-Command" ] 20 | } 21 | } 22 | }, 23 | "osx": { 24 | "options": { 25 | "shell": { 26 | "executable": "/usr/local/bin/pwsh", 27 | "args": [ "-NoProfile", "-Command" ] 28 | } 29 | } 30 | }, 31 | 32 | "tasks": [ 33 | { 34 | "label": "Clean", 35 | "type": "shell", 36 | "command": "${cwd}/build.ps1 -Task Clean -Verbose" 37 | }, 38 | { 39 | "label": "Test", 40 | "type": "shell", 41 | "command": "${cwd}/build.ps1 -Task Test -Verbose", 42 | "group": { 43 | "kind": "test", 44 | "isDefault": true 45 | }, 46 | "problemMatcher": "$pester" 47 | }, 48 | { 49 | "label": "Analyze", 50 | "type": "shell", 51 | "command": "${cwd}/build.ps1 -Task Analyze -Verbose" 52 | }, 53 | { 54 | "label": "Pester", 55 | "type": "shell", 56 | "command": "${cwd}/build.ps1 -Task Pester -Verbose", 57 | "problemMatcher": "$pester" 58 | }, 59 | { 60 | "label": "Build", 61 | "type": "shell", 62 | "command": "${cwd}/build.ps1 -Task Build -Verbose", 63 | "group": { 64 | "kind": "build", 65 | "isDefault": true 66 | } 67 | }, 68 | { 69 | "label": "Publish", 70 | "type": "shell", 71 | "command": "${cwd}/build.ps1 -Task Publish -Verbose" 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /tests/TestModule/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [0.1.0] Unreleased 9 | 10 | -------------------------------------------------------------------------------- /tests/TestModule/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, sexual identity and 10 | orientation, or sexual proclivities between consenting adults. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 26 | * Trolling, insulting/derogatory comments, and personal or political attacks 27 | * Public or private harassment 28 | * Publishing others' private information, such as a physical or electronic 29 | address, without explicit permission 30 | * Other conduct which could reasonably be considered inappropriate in a 31 | professional setting 32 | 33 | ## Our Responsibilities 34 | 35 | Project maintainers are responsible for clarifying the standards of acceptable 36 | behavior and are expected to take appropriate and fair corrective action in 37 | response to any instances of unacceptable behavior. 38 | 39 | Project maintainers have the right and responsibility to remove, edit, or 40 | reject comments, commits, code, wiki edits, issues, and other contributions 41 | that are not aligned to this Code of Conduct, or to ban temporarily or 42 | permanently any contributor for other behaviors that they deem inappropriate, 43 | threatening, offensive, or harmful. 44 | 45 | ## Scope 46 | 47 | This Code of Conduct applies both within project spaces and in public spaces 48 | when an individual is representing the project or its community. Examples of 49 | representing a project or community include using an official project e-mail 50 | address, posting via an official social media account, or acting as an appointed 51 | representative at an online or offline event. Representation of a project may be 52 | further defined and clarified by project maintainers. 53 | 54 | ## Enforcement 55 | 56 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 57 | reported by contacting the project team at [somebody@example.com](mailto:somebody@example.com). All 58 | complaints will be reviewed and investigated and will result in a response that 59 | is deemed necessary and appropriate to the circumstances. The project team is 60 | obligated to maintain confidentiality with regard to the reporter of an incident. 61 | Further details of specific enforcement policies may be posted separately. 62 | 63 | Project maintainers who do not follow or enforce the Code of Conduct in good 64 | faith may face temporary or permanent repercussions as determined by other 65 | members of the project's leadership. 66 | 67 | ## Attribution 68 | 69 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4.1, 70 | available at [http://contributor-covenant.org/version/1/4/1][version] 71 | 72 | [homepage]: http://contributor-covenant.org 73 | [version]: http://contributor-covenant.org/version/1/4/ 74 | 75 | -------------------------------------------------------------------------------- /tests/TestModule/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Brandon Olin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tests/TestModule/README.md: -------------------------------------------------------------------------------- 1 | # TestModule 2 | 3 | Test module for PowerShellBuild 4 | 5 | ## Overview 6 | 7 | ## Installation 8 | 9 | ## Examples 10 | 11 | -------------------------------------------------------------------------------- /tests/TestModule/TestModule/Private/GetHelloWorld.ps1: -------------------------------------------------------------------------------- 1 | function GetHelloWorld { 2 | 'Hello world' 3 | } 4 | -------------------------------------------------------------------------------- /tests/TestModule/TestModule/Private/excludemealso.ps1: -------------------------------------------------------------------------------- 1 | '=== EXCLUDE ME =========' 2 | -------------------------------------------------------------------------------- /tests/TestModule/TestModule/Public/Get-HelloWorld.ps1: -------------------------------------------------------------------------------- 1 | Function Get-HelloWorld { 2 | <# 3 | .SYNOPSIS 4 | Returns Hello world 5 | .DESCRIPTION 6 | Returns Hello world 7 | .EXAMPLE 8 | PS> Get-HelloWorld 9 | 10 | Runs the command 11 | #> 12 | $value = GetHelloWorld 13 | $value 14 | } 15 | -------------------------------------------------------------------------------- /tests/TestModule/TestModule/TestModule.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | RootModule = 'TestModule.psm1' 3 | ModuleVersion = '0.1.0' 4 | GUID = 'f6b27f39-d2fd-4620-b895-9dc1ac4e7768' 5 | Author = 'Brandon Olin' 6 | CompanyName = 'Community' 7 | Copyright = '(c) Brandon Olin. All rights reserved.' 8 | Description = 'Test module for PowerShellBuild' 9 | PowerShellVersion = '3.0' 10 | RequiredModules = @() 11 | FunctionsToExport = '*' 12 | CmdletsToExport = @() 13 | VariablesToExport = @() 14 | AliasesToExport = @() 15 | PrivateData = @{ 16 | PSData = @{ 17 | # Tags = @() 18 | # LicenseUri = '' 19 | # ProjectUri = '' 20 | # IconUri = '' 21 | # ReleaseNotes = '' 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/TestModule/TestModule/TestModule.psm1: -------------------------------------------------------------------------------- 1 | # I'm some code in the src PSM1 2 | -------------------------------------------------------------------------------- /tests/TestModule/TestModule/dontcopy/garbage.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psake/PowerShellBuild/63340deec03da255d163a17f30647fdd04cbe2c1/tests/TestModule/TestModule/dontcopy/garbage.txt -------------------------------------------------------------------------------- /tests/TestModule/TestModule/excludeme.txt: -------------------------------------------------------------------------------- 1 | === EXCLUDE ME === 2 | -------------------------------------------------------------------------------- /tests/TestModule/TestModule/stuff/copymealways.txt: -------------------------------------------------------------------------------- 1 | === COPY ME ALWAYS === 2 | -------------------------------------------------------------------------------- /tests/TestModule/Tests/Help.tests.ps1: -------------------------------------------------------------------------------- 1 | # Taken with love from @juneb_get_help (https://raw.githubusercontent.com/juneb/PesterTDD/master/Module.Help.Tests.ps1) 2 | 3 | Describe 'Help' { 4 | $testCases = Get-Command -Module $env:BHProjectName -CommandType Cmdlet, Function | ForEach-Object { 5 | @{ 6 | Name = $_.Name 7 | Command = $_ 8 | } 9 | } 10 | 11 | BeforeAll { 12 | $commonParameters = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 13 | 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable', 'Confirm', 'Whatif' 14 | } 15 | 16 | # No auto-generated help 17 | Context 'Auto-generation' { 18 | it 'Help for [] should not be auto-generated' -TestCases $testCases { 19 | param($Name, $Command) 20 | 21 | $help = Get-Help $Name -ErrorAction SilentlyContinue 22 | $help.Synopsis | Should -Not -BeLike '*`[``]*' 23 | } 24 | } 25 | 26 | 27 | # Should have a description for every function 28 | Context 'Help description' { 29 | It 'Help for [] has a description' -TestCases $testCases { 30 | param($Name, $Command) 31 | 32 | $help = Get-Help $Name -ErrorAction SilentlyContinue 33 | $help.Description | Should -Not -BeNullOrEmpty 34 | } 35 | } 36 | 37 | # Should be at least one example per command 38 | Context 'Examples' { 39 | It 'Help for [] has example code' -TestCases $testCases { 40 | param($Name, $Command) 41 | 42 | $help = Get-Help $Name -ErrorAction SilentlyContinue 43 | ($help.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty 44 | } 45 | } 46 | 47 | # Parameter help 48 | Context 'Parameter help' { 49 | It '[] has help for every parameter' -TestCases $testCases { 50 | param($Name, $Command) 51 | 52 | $help = Get-Help $Name -ErrorAction SilentlyContinue 53 | $parameters = $Command.ParameterSets.Parameters | 54 | Sort-Object -Property Name -Unique | 55 | Where-Object { $_.Name -notin $commonParameters } 56 | $parameterNames = $parameters.Name 57 | 58 | # Without the filter, WhatIf and Confirm parameters are still flagged in "finds help parameter in code" test 59 | $helpParameters = $help.Parameters.Parameter | 60 | Where-Object { $_.Name -notin $commonParameters } | 61 | Sort-Object -Property Name -Unique 62 | $helpParameterNames = $helpParameters.Name 63 | 64 | foreach ($parameter in $parameters) { 65 | $parameterName = $parameter.Name 66 | $parameterHelp = $help.parameters.parameter | Where-Object Name -eq $parameterName 67 | $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty 68 | 69 | $codeMandatory = $parameter.IsMandatory.toString() 70 | $parameterHelp.Required | Should -Be $codeMandatory 71 | 72 | $codeType = $parameter.ParameterType.Name 73 | # To avoid calling Trim method on a null object. 74 | $helpType = if ($parameterHelp.parameterValue) { $parameterHelp.parameterValue.Trim() } 75 | $helpType | Should -Be $codeType 76 | } 77 | } 78 | } 79 | 80 | # Links are valid 81 | Context 'Links' { 82 | It 'Help for [] has valid links' -TestCases $testCases { 83 | param($Name, $Command) 84 | 85 | $help = Get-Help $Name -ErrorAction SilentlyContinue 86 | $link = $help.relatedLinks.navigationLink.uri 87 | foreach ($link in $links) { 88 | $Results = Invoke-WebRequest -Uri $link -UseBasicParsing 89 | $Results.StatusCode | Should -Be '200' 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/TestModule/Tests/Manifest.tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'Module manifest' { 2 | 3 | BeforeAll { 4 | $moduleName = $env:BHProjectName 5 | $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest 6 | $outputDir = [IO.Path]::Combine($env:BHProjectPath, 'Output') 7 | $outputModDir = [IO.Path]::Combine($outputDir, $env:BHProjectName) 8 | $outputModVerDir = [IO.Path]::Combine($outputModDir, $manifest.ModuleVersion) 9 | $outputManifestPath = [IO.Path]::Combine($outputModVerDir, "$($moduleName).psd1") 10 | $changelogPath = [IO.Path]::Combine($env:BHProjectPath, 'CHANGELOG.md') 11 | } 12 | 13 | Context 'Validation' { 14 | 15 | $script:manifest = $null 16 | 17 | It 'has a valid manifest' { 18 | { 19 | $script:manifest = Test-ModuleManifest -Path $outputManifestPath -Verbose:$false -ErrorAction Stop -WarningAction SilentlyContinue 20 | } | Should -Not -Throw 21 | } 22 | 23 | It 'has a valid name in the manifest' { 24 | $script:manifest.Name | Should -Be $env:BHProjectName 25 | } 26 | 27 | It 'has a valid root module' { 28 | $script:manifest.RootModule | Should -Be "$($moduleName).psm1" 29 | } 30 | 31 | It 'has a valid version in the manifest' { 32 | $script:manifest.Version -as [Version] | Should -Not -BeNullOrEmpty 33 | } 34 | 35 | It 'has a valid description' { 36 | $script:manifest.Description | Should -Not -BeNullOrEmpty 37 | } 38 | 39 | It 'has a valid author' { 40 | $script:manifest.Author | Should -Not -BeNullOrEmpty 41 | } 42 | 43 | It 'has a valid guid' { 44 | { 45 | [guid]::Parse($script:manifest.Guid) 46 | } | Should -Not -Throw 47 | } 48 | 49 | It 'has a valid copyright' { 50 | $script:manifest.CopyRight | Should -Not -BeNullOrEmpty 51 | } 52 | 53 | $script:changelogVersion = $null 54 | It 'has a valid version in the changelog' { 55 | foreach ($line in (Get-Content $changelogPath)) { 56 | if ($line -match "^##\s\[(?(\d+\.){1,3}\d+)\]") { 57 | $script:changelogVersion = $matches.Version 58 | break 59 | } 60 | } 61 | $script:changelogVersion | Should -Not -BeNullOrEmpty 62 | $script:changelogVersion -as [Version] | Should -Not -BeNullOrEmpty 63 | } 64 | 65 | It 'changelog and manifest versions are the same' { 66 | $script:changelogVersion -as [Version] | Should -Be ($script:manifest.Version -as [Version]) 67 | } 68 | 69 | if (Get-Command git.exe -ErrorAction SilentlyContinue) { 70 | $script:tagVersion = $null 71 | It 'is tagged with a valid version' -skip { 72 | $thisCommit = git.exe log --decorate --oneline HEAD~1..HEAD 73 | 74 | if ($thisCommit -match 'tag:\s*(\d+(?:\.\d+)*)') { 75 | $script:tagVersion = $matches[1] 76 | } 77 | 78 | $script:tagVersion | Should -Not -BeNullOrEmpty 79 | $script:tagVersion -as [Version] | Should -Not -BeNullOrEmpty 80 | } 81 | 82 | It 'all versions are the same' { 83 | $script:changelogVersion -as [Version] | Should -Be ( $script:manifest.Version -as [Version] ) 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/TestModule/Tests/Meta.tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'Text files formatting' { 2 | 3 | BeforeAll { 4 | Set-StrictMode -Version latest 5 | 6 | # Make sure MetaFixers.psm1 is loaded - it contains Get-TextFilesList 7 | Import-Module -Name ([IO.Path]::Combine($PSScriptRoot, 'MetaFixers.psm1')) -Verbose:$false -Force 8 | 9 | $projectRoot = $ENV:BHProjectPath 10 | if(-not $projectRoot) { 11 | $projectRoot = $PSScriptRoot 12 | } 13 | 14 | $allTextFiles = Get-TextFilesList $projectRoot 15 | } 16 | 17 | Context 'Files encoding' { 18 | It "Doesn't use Unicode encoding" { 19 | $unicodeFilesCount = 0 20 | $allTextFiles | Foreach-Object { 21 | if (Test-FileUnicode $_) { 22 | $unicodeFilesCount += 1 23 | Write-Warning "File $($_.FullName) contains 0x00 bytes. It's probably uses Unicode and need to be converted to UTF-8. Use Fixer 'Get-UnicodeFilesList `$pwd | ConvertTo-UTF8'." 24 | } 25 | } 26 | $unicodeFilesCount | Should -Be 0 27 | } 28 | } 29 | 30 | Context 'Indentations' { 31 | It 'Uses spaces for indentation, not tabs' { 32 | $totalTabsCount = 0 33 | $allTextFiles | Foreach-Object { 34 | $fileName = $_.FullName 35 | (Get-Content $_.FullName -Raw) | Select-String "`t" | Foreach-Object { 36 | Write-Warning "There are tab in $fileName. Use Fixer 'Get-TextFilesList `$pwd | ConvertTo-SpaceIndentation'." 37 | $totalTabsCount++ 38 | } 39 | } 40 | $totalTabsCount | Should -Be 0 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/TestModule/Tests/MetaFixers.psm1: -------------------------------------------------------------------------------- 1 | # Taken with love from https://github.com/PowerShell/DscResource.Tests/blob/master/MetaFixers.psm1 2 | 3 | <# 4 | This module helps fix problems, found by Meta.Tests.ps1 5 | #> 6 | 7 | #$ErrorActionPreference = 'stop' 8 | Set-StrictMode -Version latest 9 | 10 | function ConvertTo-UTF8() { 11 | [CmdletBinding()] 12 | [OutputType([void])] 13 | param( 14 | [Parameter(Mandatory, ValueFromPipeline)] 15 | [System.IO.FileInfo]$FileInfo 16 | ) 17 | 18 | process { 19 | $content = Get-Content -Raw -Encoding Unicode -Path $FileInfo.FullName 20 | [System.IO.File]::WriteAllText($FileInfo.FullName, $content, [System.Text.Encoding]::UTF8) 21 | } 22 | } 23 | 24 | function ConvertTo-SpaceIndentation() { 25 | [CmdletBinding()] 26 | [OutputType([void])] 27 | param( 28 | [Parameter(Mandatory, ValueFromPipeline)] 29 | [System.IO.FileInfo]$FileInfo 30 | ) 31 | 32 | process { 33 | $content = (Get-Content -Raw -Path $FileInfo.FullName) -replace "`t", ' ' 34 | [System.IO.File]::WriteAllText($FileInfo.FullName, $content) 35 | } 36 | } 37 | 38 | function Get-TextFilesList { 39 | [CmdletBinding()] 40 | [OutputType([System.IO.FileInfo])] 41 | param( 42 | [Parameter(Mandatory)] 43 | [string]$Root 44 | ) 45 | Get-ChildItem -Path $Root -File -Recurse | 46 | Where-Object { @('.gitignore', '.gitattributes', '.ps1', '.psm1', '.psd1', '.json', '.xml', '.cmd', '.mof') -contains $_.Extension } 47 | } 48 | 49 | function Test-FileUnicode { 50 | [CmdletBinding()] 51 | [OutputType([bool])] 52 | param( 53 | [Parameter(Mandatory, ValueFromPipeline)] 54 | [System.IO.FileInfo]$FileInfo 55 | ) 56 | 57 | process { 58 | $path = $FileInfo.FullName 59 | $bytes = [System.IO.File]::ReadAllBytes($path) 60 | $zeroBytes = @($bytes -eq 0) 61 | return [bool]$zeroBytes.Length 62 | 63 | } 64 | } 65 | 66 | function Get-UnicodeFilesList() { 67 | [CmdletBinding()] 68 | [OutputType([System.IO.FileInfo])] 69 | param( 70 | [Parameter(Mandatory)] 71 | [string]$Root 72 | ) 73 | 74 | Get-TextFilesList $Root | Where-Object { Test-FileUnicode $_ } 75 | } 76 | -------------------------------------------------------------------------------- /tests/TestModule/Tests/ScriptAnalyzerSettings.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | 3 | } 4 | -------------------------------------------------------------------------------- /tests/TestModule/Tests/a_InModuleScope.tests.ps1: -------------------------------------------------------------------------------- 1 | InModuleScope TestModule { 2 | Describe 'MyModule' { 3 | Context 'Private' { 4 | It 'Can test a private module' { 5 | (GetHelloWorld) | Should -BeExactly 'Hello world' 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/TestModule/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: build_pwsh_win2016 3 | pool: 4 | vmImage: vs2017-win2016 5 | steps: 6 | - pwsh: ./build.ps1 -Task Test -Bootstrap -Verbose 7 | displayName: 'Build and Test' 8 | - task: PublishTestResults@2 9 | inputs: 10 | testRunner: 'NUnit' 11 | testResultsFiles: '**/out/testResults.xml' 12 | testRunTitle: 'pwsh_win2016' 13 | displayName: 'Publish Test Results' 14 | 15 | -------------------------------------------------------------------------------- /tests/TestModule/build.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding(DefaultParameterSetName = 'Task')] 2 | param( 3 | # Build task(s) to execute 4 | [parameter(ParameterSetName = 'task', position = 0)] 5 | [string[]]$Task = 'default', 6 | 7 | # Bootstrap dependencies 8 | [switch]$Bootstrap, 9 | 10 | # List available build tasks 11 | [parameter(ParameterSetName = 'Help')] 12 | [switch]$Help, 13 | 14 | [ValidateSet('InvokeBuild', 'psake')] 15 | [string]$BuildTool = 'psake', 16 | 17 | # Optional properties to pass to psake 18 | [hashtable]$Properties 19 | ) 20 | 21 | $ErrorActionPreference = 'Stop' 22 | 23 | # Bootstrap dependencies 24 | if ($Bootstrap.IsPresent) { 25 | Get-PackageProvider -Name Nuget -ForceBootstrap | Out-Null 26 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted 27 | if ((Test-Path -Path ./requirements.psd1)) { 28 | if (-not (Get-Module -Name PSDepend -ListAvailable)) { 29 | Install-Module -Name PSDepend -Repository PSGallery -Scope CurrentUser -Force 30 | } 31 | Import-Module -Name PSDepend -Verbose:$false 32 | Invoke-PSDepend -Path './requirements.psd1' -Install -Import -Force -WarningAction SilentlyContinue 33 | } else { 34 | Write-Warning "No [requirements.psd1] found. Skipping build dependency installation." 35 | } 36 | } 37 | 38 | if ($BuildTool -eq 'psake') { 39 | if (Get-Module InvokeBuild) {Remove-Module InvokeBuild -Force} 40 | # Execute psake task(s) 41 | $psakeFile = './psakeFile.ps1' 42 | if ($PSCmdlet.ParameterSetName -eq 'Help') { 43 | Get-PSakeScriptTasks -buildFile $psakeFile | 44 | Format-Table -Property Name, Description, Alias, DependsOn 45 | } else { 46 | Set-BuildEnvironment -Force 47 | Invoke-psake -buildFile $psakeFile -taskList $Task -nologo -properties $Properties 48 | exit ([int](-not $psake.build_success)) 49 | } 50 | } else { 51 | if ($PSCmdlet.ParameterSetName -eq 'Help') { 52 | Invoke-Build -File ./.build.ps1 ? 53 | } else { 54 | # Execute IB task(s) 55 | Import-Module InvokeBuild 56 | if ($Task -eq 'Default') {$Task = '.'} 57 | Invoke-Build -File ./.build.ps1 -Task $Task 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/TestModule/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: mkdocs@testmodule.com 2 | repo_url: https://github.com/psake/PowerShellBuild 3 | theme: readthedocs 4 | 5 | -------------------------------------------------------------------------------- /tests/TestModule/psakeFile.ps1: -------------------------------------------------------------------------------- 1 | Import-Module ../../Output/PowerShellBuild -Force 2 | 3 | properties { 4 | # Pester can build the module using both scenarios 5 | if (Test-Path -Path 'Variable:\PSBuildCompile') { 6 | $PSBPreference.Build.CompileModule = $global:PSBuildCompile 7 | } else { 8 | $PSBPreference.Build.CompileModule = $true 9 | } 10 | 11 | # Always copy these 12 | $PSBPreference.Build.CopyDirectories = ('stuff') 13 | 14 | # Don't ever copy these 15 | $PSBPreference.Build.Exclude = ('excludeme.txt', 'excludemealso*', 'dontcopy') 16 | 17 | # If compiling, insert headers/footers for entire PSM1 and for each inserted function 18 | $PSBPreference.Build.CompileHeader = '# Module Header' + [Environment]::NewLine 19 | $PSBPreference.Build.CompileFooter = '# Module Footer' 20 | $PSBPreference.Build.CompileScriptHeader = '# Function header' 21 | $PSBPreference.Build.CompileScriptFooter = '# Function footer' + [Environment]::NewLine 22 | 23 | # So Pester InModuleScope works 24 | $PSBPreference.Test.ImportModule = $true 25 | $PSBPreference.Test.CodeCoverage.Enabled = $true 26 | $PSBPreference.Test.CodeCoverage.Threshold = 0.0 27 | $PSBPreference.Test.CodeCoverage.OutputFile = 'cc.xml' 28 | 29 | # Override the default output directory 30 | $PSBPreference.Build.OutDir = 'Output' 31 | } 32 | 33 | task default -depends Build 34 | 35 | task Build -FromModule PowerShellBuild -minimumVersion 0.5.0 36 | -------------------------------------------------------------------------------- /tests/TestModule/requirements.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | PSDependOptions = @{ 3 | Target = 'CurrentUser' 4 | } 5 | 'InvokeBuild' = @{ 6 | Version = '5.5.1' 7 | } 8 | 'Pester' = @{ 9 | Version = '4.8.1' 10 | Parameters = @{ 11 | SkipPublisherCheck = $true 12 | } 13 | } 14 | 'psake' = @{ 15 | Version = '4.8.0' 16 | } 17 | 'BuildHelpers' = @{ 18 | Version = '2.0.10' 19 | } 20 | 'PowerShellBuild' = @{ 21 | Version = '0.5.0' 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/build.tests.ps1: -------------------------------------------------------------------------------- 1 | # spell-checker:ignore excludeme 2 | Describe 'Build' { 3 | 4 | BeforeAll { 5 | # Hack for GH Actions 6 | # For some reason, the TestModule build process create the output in the project root 7 | # and not relative to it's own build file. 8 | if ($env:GITHUB_ACTION) { 9 | $script:testModuleOutputPath = [IO.Path]::Combine($env:BHProjectPath, 'Output', 'TestModule', '0.1.0') 10 | } else { 11 | $script:testModuleOutputPath = [IO.Path]::Combine($env:BHProjectPath, 'tests', 'TestModule', 'Output', 'TestModule', '0.1.0') 12 | } 13 | } 14 | 15 | Context 'Compile module' { 16 | BeforeAll { 17 | 18 | Write-Host "PSScriptRoot: $PSScriptRoot" 19 | Write-Host "OutputPath: $script:testModuleOutputPath" 20 | 21 | # build is PS job so psake doesn't freak out because it's nested 22 | Start-Job -ScriptBlock { 23 | Set-Location $using:PSScriptRoot/TestModule 24 | $global:PSBuildCompile = $true 25 | ./build.ps1 -Task Build 26 | } | Wait-Job 27 | } 28 | 29 | AfterAll { 30 | Remove-Item $script:testModuleOutputPath -Recurse -Force 31 | } 32 | 33 | It 'Creates module' { 34 | $script:testModuleOutputPath | Should -Exist 35 | } 36 | 37 | It 'Has PSD1 and monolithic PSM1' { 38 | (Get-ChildItem -Path $script:testModuleOutputPath -File).Count | Should -Be 2 39 | "$script:testModuleOutputPath/TestModule.psd1" | Should -Exist 40 | "$script:testModuleOutputPath/TestModule.psm1" | Should -Exist 41 | "$script:testModuleOutputPath/Public" | Should -Not -Exist 42 | "$script:testModuleOutputPath/Private" | Should -Not -Exist 43 | } 44 | 45 | It 'Has module header text' { 46 | "$script:testModuleOutputPath/TestModule.psm1" | Should -FileContentMatch '# Module Header' 47 | } 48 | 49 | It 'Has module footer text' { 50 | "$script:testModuleOutputPath/TestModule.psm1" | Should -FileContentMatch '# Module Footer' 51 | } 52 | 53 | It 'Has function header text' { 54 | "$script:testModuleOutputPath/TestModule.psm1" | Should -FileContentMatch '# Function header' 55 | } 56 | 57 | It 'Has function footer text' { 58 | "$script:testModuleOutputPath/TestModule.psm1" | Should -FileContentMatch '# Function footer' 59 | } 60 | 61 | It 'Does not contain excluded files' { 62 | (Get-ChildItem -Path $script:testModuleOutputPath -File -Filter '*excludeme*' -Recurse).Count | Should -Be 0 63 | "$script:testModuleOutputPath/TestModule.psm1" | Should -Not -FileContentMatch '=== EXCLUDE ME ===' 64 | } 65 | 66 | It 'Has MAML help XML' { 67 | "$script:testModuleOutputPath/en-US/TestModule-help.xml" | Should -Exist 68 | } 69 | } 70 | 71 | Context 'Dot-sourced module' { 72 | BeforeAll { 73 | # build is PS job so psake doesn't freak out because it's nested 74 | Start-Job -ScriptBlock { 75 | Set-Location $using:PSScriptRoot/TestModule 76 | $global:PSBuildCompile = $false 77 | ./build.ps1 -Task Build 78 | } | Wait-Job 79 | } 80 | 81 | AfterAll { 82 | Remove-Item $script:testModuleOutputPath -Recurse -Force 83 | } 84 | 85 | It 'Creates module' { 86 | $script:testModuleOutputPath | Should -Exist 87 | } 88 | 89 | It 'Has PSD1 and dot-sourced functions' { 90 | (Get-ChildItem -Path $script:testModuleOutputPath).Count | Should -Be 6 91 | "$script:testModuleOutputPath/TestModule.psd1" | Should -Exist 92 | "$script:testModuleOutputPath/TestModule.psm1" | Should -Exist 93 | "$script:testModuleOutputPath/Public" | Should -Exist 94 | "$script:testModuleOutputPath/Private" | Should -Exist 95 | } 96 | 97 | It 'Does not contain excluded stuff' { 98 | (Get-ChildItem -Path $script:testModuleOutputPath -File -Filter '*excludeme*' -Recurse).Count | Should -Be 0 99 | } 100 | 101 | It 'Has MAML help XML' { 102 | "$script:testModuleOutputPath/en-US/TestModule-help.xml" | Should -Exist 103 | } 104 | } 105 | } 106 | --------------------------------------------------------------------------------