├── .github ├── CODEOWNERS ├── FUNDING.yml ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── feature_request.yml │ └── bug_report.yml └── workflows │ ├── build-test-and-deploy-powershell-module.yml │ └── build-and-test-powershell-module.yml ├── .vscode ├── extensions.json └── tasks.json ├── src └── ScriptModuleRepositoryTemplate │ ├── TemplateRepoFiles │ ├── _.vscode │ │ ├── extensions.json │ │ └── tasks.json │ ├── src │ │ └── __NewModuleName__ │ │ │ ├── __NewModuleName__.psm1 │ │ │ ├── __NewModuleName__.Tests.ps1 │ │ │ └── __NewModuleName__.psd1 │ ├── Changelog.md │ ├── _.github │ │ ├── pull_request_template.md │ │ ├── ISSUE_TEMPLATE │ │ │ ├── feature_request.yml │ │ │ └── bug_report.yml │ │ └── workflows │ │ │ ├── build-and-test-powershell-module.yml │ │ │ └── build-test-and-deploy-powershell-module.yml │ ├── _.editorconfig │ ├── License.md │ ├── _.gitattributes │ ├── deploy │ │ └── Invoke-SmokeTests.ps1 │ ├── _.cspell.json │ ├── _.devcontainer │ │ └── devcontainer.json │ ├── docs │ │ └── Contributing.md │ ├── ReadMe.md │ └── _.gitignore │ ├── ScriptModuleRepositoryTemplate.Tests.ps1 │ ├── ScriptModuleRepositoryTemplate.psd1 │ └── ScriptModuleRepositoryTemplate.psm1 ├── Changelog.md ├── .editorconfig ├── License.md ├── .gitattributes ├── .cspell.json ├── .devcontainer └── devcontainer.json ├── deploy └── Invoke-SmokeTests.ps1 ├── docs ├── ArchitectureDecisionRecords │ ├── 001-Use-separate-repo-files-for-template.md │ └── ArchitectureDecisionRecords.md └── Contributing.md ├── _InitializeRepository.ps1 ├── .gitignore └── ReadMe.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Auto-include these teams/individuals on all PRs. 2 | * @deadlydog 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "EditorConfig.EditorConfig", 4 | "ms-vscode.powershell", 5 | "streetsidesoftware.code-spell-checker", 6 | "TylerLeonhardt.vscode-inline-values-powershell", 7 | "yzhang.markdown-all-in-one" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "EditorConfig.EditorConfig", 4 | "ms-vscode.powershell", 5 | "streetsidesoftware.code-spell-checker", 6 | "TylerLeonhardt.vscode-inline-values-powershell" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/src/__NewModuleName__/__NewModuleName__.psm1: -------------------------------------------------------------------------------- 1 | # UPDATE ME: This is just example code. Replace this file's contents with your module code. 2 | 3 | function Get-HelloWorld { 4 | [CmdletBinding()] 5 | Param () 6 | 7 | Write-Output "Hello, World!" 8 | } 9 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This page is a list of _notable_ changes made in each version. 4 | 5 | ## v1.0.0 - January 1, 2099 6 | 7 | Features: 8 | 9 | - Initial release 10 | 11 | Fixes: 12 | 13 | - Fixes 14 | 15 | Community contributions: 16 | 17 | - @person: Contribution 18 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/src/__NewModuleName__/__NewModuleName__.Tests.ps1: -------------------------------------------------------------------------------- 1 | using module '.\__NewModuleName__.psm1' 2 | 3 | # UPDATE ME: This is just example code. Replace the code below with your module's tests. 4 | Describe 'Get-HelloWorld' { 5 | It 'Should return "Hello, World!"' { 6 | $expected = 'Hello, World!' 7 | $result = Get-HelloWorld 8 | $result | Should -Be $expected 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: deadlydog # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.paypal.me/deadlydogDan # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | A brief summary of the change this PR brings. 4 | 5 | ### Checklist 6 | 7 | Addresses issue #ISSUE_NUMBER (if applicable) 8 | 9 | - [ ] Tests have been added for this code change (if applicable) 10 | - [ ] Docs have been added or updated (if applicable) 11 | - [ ] Code format follows the project style 12 | - [ ] All new and existing tests passed 13 | 14 | ### What type of changes does this PR include 15 | 16 | - [ ] Bug fix (non-breaking change which fixes an issue) 17 | - [ ] New feature (non-breaking change which adds functionality) 18 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 19 | 20 | ### Describe the change in more detail 21 | 22 | What is the motivation for this change? What does it do? What problem does it solve? 23 | 24 | ### Additional information 25 | 26 | Any other information about the change, such as screenshots, example flows, etc. 27 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This page is a list of _notable_ changes made in each version. 4 | 5 | ## v1.2.0 - March 27, 2025 6 | 7 | Features: 8 | 9 | - Add VS Code task to run CSpell spellcheck when building, as well as a stand-alone VS Code task. 10 | - Update dev container to the latest PowerShell image and have it install PSScriptAnalyzer, Pester, and CSpell. 11 | - Update default Contributing.md to include information around local development and building. 12 | 13 | ## v1.1.0 - April 20, 2024 14 | 15 | Features: 16 | 17 | - Run Pester tests during the build on Windows PowerShell too, to catch backward-incompatible changes earlier. 18 | 19 | Fixes: 20 | 21 | - Use backslash instead of forward-slash to import module in Pester tests for Windows PowerShell backward compatibility. 22 | 23 | ## v1.0.0 - April 13, 2024 24 | 25 | Features: 26 | 27 | - Initial release. 28 | Currently only supports creating repos for GitHub Actions workflows that publish to the public PowerShell Gallery. 29 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file should only include settings that affect the physical contents of the file, not just how it appears in an editor. 2 | # Do not include personal preference presentation settings like a tab's `indent_size` in this file; those should be specified 3 | # in a parent .editorconfig file outside of the repository. 4 | # v1.7 - Source: https://gist.github.com/deadlydog/bd000162e85c155b243a712c16f7411c 5 | 6 | # Ensure that personal preference presentation settings can be inherited from parent .editorconfig files. 7 | root = false 8 | 9 | #### Core EditorConfig Options #### 10 | 11 | [*] 12 | charset = utf-8 13 | indent_style = tab 14 | end_of_line = crlf 15 | insert_final_newline = true 16 | trim_trailing_whitespace = true 17 | 18 | # For some languages, the number of spaces used for indentation matters. 19 | [*.{tf,md,psd1,pp,yml,yaml}] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | #### .NET Coding Conventions #### 24 | 25 | csharp_style_namespace_declarations = file_scoped 26 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | A brief summary of the change this PR brings. 4 | 5 | ### Checklist 6 | 7 | Addresses issue #ISSUE_NUMBER (if applicable) 8 | 9 | - [ ] Tests have been added for this code change (if applicable) 10 | - [ ] Docs have been added or updated (if applicable) 11 | - [ ] Code format follows the project style 12 | - [ ] All new and existing tests passed 13 | 14 | ### What type of changes does this PR include 15 | 16 | - [ ] Bug fix (non-breaking change which fixes an issue) 17 | - [ ] New feature (non-breaking change which adds functionality) 18 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 19 | 20 | ### Describe the change in more detail 21 | 22 | What is the motivation for this change? What does it do? What problem does it solve? 23 | 24 | ### Additional information 25 | 26 | Any other information about the change, such as screenshots, example flows, etc. 27 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.editorconfig: -------------------------------------------------------------------------------- 1 | # This file should only include settings that affect the physical contents of the file, not just how it appears in an editor. 2 | # Do not include personal preference presentation settings like a tab's `indent_size` in this file; those should be specified 3 | # in a parent .editorconfig file outside of the repository. 4 | # v1.7 - Source: https://gist.github.com/deadlydog/bd000162e85c155b243a712c16f7411c 5 | 6 | # Ensure that personal preference presentation settings can be inherited from parent .editorconfig files. 7 | root = false 8 | 9 | #### Core EditorConfig Options #### 10 | 11 | [*] 12 | charset = utf-8 13 | indent_style = tab 14 | end_of_line = crlf 15 | insert_final_newline = true 16 | trim_trailing_whitespace = true 17 | 18 | # For some languages, the number of spaces used for indentation matters. 19 | [*.{tf,md,psd1,pp,yml,yaml}] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | #### .NET Coding Conventions #### 24 | 25 | csharp_style_namespace_declarations = file_scoped 26 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Daniel Schroeder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Use 'git add --renormalize .' to apply rules to files added to the repository before the .gitattributes file. 2 | 3 | # Have GitHub language statistics ignore generated files. 4 | # More info: https://github.com/github-linguist/linguist/blob/master/docs/overrides.md 5 | #generatedFiles/** linguist-generated=true 6 | 7 | # Auto detect text files and perform LF normalization. 8 | * text=auto 9 | 10 | # Documents 11 | *.doc diff=astextplain 12 | *.DOC diff=astextplain 13 | *.docx diff=astextplain 14 | *.DOCX diff=astextplain 15 | *.dot diff=astextplain 16 | *.DOT diff=astextplain 17 | *.pdf diff=astextplain 18 | *.PDF diff=astextplain 19 | *.rtf diff=astextplain 20 | *.RTF diff=astextplain 21 | *.md text 22 | *.adoc text 23 | *.textile text 24 | *.mustache text 25 | *.csv text 26 | *.tab text 27 | *.tsv text 28 | *.sql text 29 | 30 | # Graphics 31 | *.png binary 32 | *.jpg binary 33 | *.jpeg binary 34 | *.gif binary 35 | *.tif binary 36 | *.tiff binary 37 | *.ico binary 38 | # SVG treated as an asset (binary) by default, but it's actually just a text file so let's treat it as one. 39 | #*.svg binary 40 | *.svg text 41 | *.eps binary 42 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) __CurrentYear__ __IndividualOrOrganizationName__ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.gitattributes: -------------------------------------------------------------------------------- 1 | # Use 'git add --renormalize .' to apply rules to files added to the repository before the .gitattributes file. 2 | 3 | # Have GitHub language statistics ignore generated files. 4 | # More info: https://github.com/github-linguist/linguist/blob/master/docs/overrides.md 5 | #generatedFiles/** linguist-generated=true 6 | 7 | # Auto detect text files and perform LF normalization. 8 | * text=auto 9 | 10 | # Documents 11 | *.doc diff=astextplain 12 | *.DOC diff=astextplain 13 | *.docx diff=astextplain 14 | *.DOCX diff=astextplain 15 | *.dot diff=astextplain 16 | *.DOT diff=astextplain 17 | *.pdf diff=astextplain 18 | *.PDF diff=astextplain 19 | *.rtf diff=astextplain 20 | *.RTF diff=astextplain 21 | *.md text 22 | *.adoc text 23 | *.textile text 24 | *.mustache text 25 | *.csv text 26 | *.tab text 27 | *.tsv text 28 | *.sql text 29 | 30 | # Graphics 31 | *.png binary 32 | *.jpg binary 33 | *.jpeg binary 34 | *.gif binary 35 | *.tif binary 36 | *.tiff binary 37 | *.ico binary 38 | # SVG treated as an asset (binary) by default, but it's actually just a text file so let's treat it as one. 39 | #*.svg binary 40 | *.svg text 41 | *.eps binary 42 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/deploy/Invoke-SmokeTests.ps1: -------------------------------------------------------------------------------- 1 | # These tests are runs as part of the deployment process to ensure the newly published module is working as expected. 2 | # These tests run against the installed module, not the source code, so they are a real-world test and should not use mocks. 3 | # Since mocks are not used, be careful to not rely on state stored on the machine, such as a module configuration file. 4 | # This is a great place to put tests that differ between operating systems, since they will be ran on multiple platforms. 5 | # Keep in mind that these tests can only call the public functions in the module, not the private functions. 6 | # To run these tests on your local machine, see the comments in the BeforeAll block. 7 | 8 | BeforeAll { 9 | Import-Module -Name '__NewModuleName__' -Force 10 | 11 | # To run these tests on your local machine, comment out the Import-Module command above and uncomment the one below. 12 | # Do this to use the module version from source code, not the installed version. 13 | # This is necessary to test functionality that you've added to the module, but have not yet published and installed. 14 | # Import-Module "$PSScriptRoot\..\src\__NewModuleName__" -Force 15 | } 16 | 17 | Describe 'Get-HelloWorld' { 18 | It 'Should return "Hello, World!"' { 19 | $expected = 'Hello, World!' 20 | $result = Get-HelloWorld 21 | $result | Should -Be $expected 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.cspell.json: -------------------------------------------------------------------------------- 1 | // This is the configuration file for cspell (code spell check). 2 | // Use this to define legitimate words not in the dictionary, and other words the spellcheck should ignore. 3 | // Used by the streetsidesoftware.code-spell-checker VS Code extension and streetsidesoftware/cspell-action@v3 GitHub Action. 4 | // For more information on configuration, see https://cspell.org/configuration/. 5 | { 6 | "version": "0.2", 7 | "language": "en", 8 | "ignorePaths": [ 9 | ".devcontainer/devcontainer.json", 10 | ".github/workflows/*.yml", 11 | ".gitignore" 12 | ], 13 | "words": [ 14 | "behaviour", 15 | "gif", 16 | "gifs", 17 | "hashtable", 18 | "remoting", 19 | "runspace", 20 | "runspaces" 21 | ], 22 | "ignoreWords": [ 23 | "chsh", // Change shell 24 | "CICD", // Continuous Integration/Continuous Deployment 25 | "CODEOWNERS", // GitHub CODEOWNERS 26 | "Codespace", // GitHub Codespaces 27 | "Codespaces", // GitHub Codespaces 28 | "devcontainer", // VS Code devcontainer 29 | "devcontainers", // VS Code devcontainers 30 | "gittools", // GitHub action author 31 | "Hmmss", // Time format 32 | "jacoco", // Java code coverage 33 | "Leonhardt", // GitHub action author 34 | "madrapps", // GitHub action author 35 | "nupkg", // NuGet package 36 | "nunit", // .NET unit testing framework 37 | "pwsh", // PowerShell Core 38 | "yzhang" // GitHub action author 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | // This is the configuration file for cspell (code spell check). 2 | // Use this to define legitimate words not in the dictionary, and other words the spellcheck should ignore. 3 | // Used by the streetsidesoftware.code-spell-checker VS Code extension and streetsidesoftware/cspell-action@v3 GitHub Action. 4 | // For more information on configuration, see https://cspell.org/configuration/. 5 | { 6 | "version": "0.2", 7 | "language": "en", 8 | "ignorePaths": [ 9 | "**/_.gitattributes", 10 | "**/_.gitignore", 11 | "**/devcontainer.json", 12 | "**/build-and-test-powershell-module.yml", 13 | "**/build-test-and-deploy-powershell-module.yml", 14 | "**/bin/**", // Ignore C# build output files. 15 | "**/obj/**", // Ignore C# build output files. 16 | ".gitignore" 17 | ], 18 | "words": [ 19 | "behaviour", 20 | "gif", 21 | "gifs", 22 | "hashtable", 23 | "remoting", 24 | "runspace", 25 | "runspaces" 26 | ], 27 | "ignoreWords": [ 28 | "chsh", // Change shell 29 | "CICD", // Continuous Integration/Continuous Deployment 30 | "CODEOWNERS", // GitHub CODEOWNERS 31 | "Codespace", // GitHub Codespaces 32 | "Codespaces", // GitHub Codespaces 33 | "devcontainer", // VS Code devcontainer 34 | "devcontainers", // VS Code devcontainers 35 | "gittools", // GitHub action author 36 | "Hmmss", // Time format 37 | "jacoco", // Java code coverage 38 | "Leonhardt", // GitHub action author 39 | "madrapps", // GitHub action author 40 | "nupkg", // NuGet package 41 | "nunit", // .NET unit testing framework 42 | "pwsh", // PowerShell Core 43 | "yzhang" // GitHub action author 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature request 2 | description: Suggest an idea for this project 3 | title: "Feature Request: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if a feature request already exists for what you are suggesting. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Problem to solve 16 | description: Is your feature request related to a problem? Please describe. 17 | placeholder: | 18 | Ex. I'm always frustrated when '...'. 19 | Ex. I want to be able to '...'. 20 | validations: 21 | required: false 22 | - type: textarea 23 | attributes: 24 | label: Summary 25 | description: Describe the solution you'd like. 26 | placeholder: | 27 | A clear and concise description of what you want to happen. 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Alternative solutions 33 | description: Describe any alternative solutions you've considered that may address the issue. 34 | validations: 35 | required: false 36 | - type: textarea 37 | attributes: 38 | label: Visuals 39 | description: If applicable, add screenshots, gifs, or videos to help explain the feature you are requesting. 40 | placeholder: | 41 | Here is a mockup of the feature I am suggesting: 42 | ![Screenshot](https://example.com/screenshot.png) 43 | validations: 44 | required: false 45 | - type: textarea 46 | attributes: 47 | label: Anything else? 48 | description: Add any other context or information about the feature request here. 49 | validations: 50 | required: false 51 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature request 2 | description: Suggest an idea for this project 3 | title: "Feature Request: <title>" 4 | labels: ["enhancement"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if a feature request already exists for what you are suggesting. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Problem to solve 16 | description: Is your feature request related to a problem? Please describe. 17 | placeholder: | 18 | Ex. I'm always frustrated when '...'. 19 | Ex. I want to be able to '...'. 20 | validations: 21 | required: false 22 | - type: textarea 23 | attributes: 24 | label: Summary 25 | description: Describe the solution you'd like. 26 | placeholder: | 27 | A clear and concise description of what you want to happen. 28 | validations: 29 | required: true 30 | - type: textarea 31 | attributes: 32 | label: Alternative solutions 33 | description: Describe any alternative solutions you've considered that may address the issue. 34 | validations: 35 | required: false 36 | - type: textarea 37 | attributes: 38 | label: Visuals 39 | description: If applicable, add screenshots, gifs, or videos to help explain the feature you are requesting. 40 | placeholder: | 41 | Here is a mockup of the feature I am suggesting: 42 | ![Screenshot](https://example.com/screenshot.png) 43 | validations: 44 | required: false 45 | - type: textarea 46 | attributes: 47 | label: Anything else? 48 | description: Add any other context or information about the feature request here. 49 | validations: 50 | required: false 51 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see: https://aka.ms/devcontainer.json. 2 | // For config options, see: https://github.com/devcontainers/templates/tree/main/src/powershell. 3 | { 4 | "name": "PowerShell", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/dotnet/sdk:9.0", 7 | "features": { 8 | "ghcr.io/devcontainers/features/common-utils:2": { 9 | "installZsh": "true", 10 | "username": "vscode", 11 | "upgradePackages": "false", 12 | "nonFreePackages": "true" 13 | } //, // Uncomment this line and the one below to install the dotnet CLI. 14 | //"ghcr.io/devcontainers/features/dotnet:2": "latest" // Installs the dotnet CLI. 15 | }, 16 | 17 | // Set pwsh as the default shell for the devcontainer, install required PowerShell modules, and install NPM and CSpell. 18 | // If you do not plan to use CSpell, you can remove everything after and including 'sudo apt update'. 19 | "postCreateCommand": "sudo chsh vscode -s \"$(which pwsh)\"; pwsh -c \"Install-Module Pester -Force\"; pwsh -c \"Install-Module PSScriptAnalyzer -Force\"; sudo apt update; sudo DEBIAN_FRONTEND=noninteractive apt install -y npm; npm install cspell", 20 | 21 | // Configure tool-specific properties. 22 | "customizations": { 23 | // Configure properties specific to VS Code. 24 | "vscode": { 25 | // Set *default* container specific settings.json values on container create. 26 | "settings": { 27 | "terminal.integrated.defaultProfile.linux": "pwsh" 28 | }, 29 | // Add the IDs of extensions you want installed when the container is created. 30 | "extensions": [ 31 | "EditorConfig.EditorConfig", 32 | "ms-vscode.powershell", 33 | "streetsidesoftware.code-spell-checker", 34 | "TylerLeonhardt.vscode-inline-values-powershell", 35 | "yzhang.markdown-all-in-one" 36 | ] 37 | } 38 | } 39 | 40 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 41 | // "forwardPorts": [], 42 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 43 | // "remoteUser": "root" 44 | } 45 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see: https://aka.ms/devcontainer.json. 2 | // For config options, see: https://github.com/devcontainers/templates/tree/main/src/powershell. 3 | { 4 | "name": "PowerShell", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/dotnet/sdk:9.0", 7 | "features": { 8 | "ghcr.io/devcontainers/features/common-utils:2": { 9 | "installZsh": "true", 10 | "username": "vscode", 11 | "upgradePackages": "false", 12 | "nonFreePackages": "true" 13 | }//, // Uncomment this line and the one below to install the dotnet CLI. 14 | //"ghcr.io/devcontainers/features/dotnet:2": "latest" // Installs the dotnet CLI. 15 | }, 16 | 17 | // Set pwsh as the default shell for the devcontainer, install required PowerShell modules, and install NPM and CSpell. 18 | // If you do not plan to use CSpell, you can remove everything after and including 'sudo apt update'. 19 | "postCreateCommand": "sudo chsh vscode -s \"$(which pwsh)\"; pwsh -c \"Install-Module Pester -Force\"; pwsh -c \"Install-Module PSScriptAnalyzer -Force\"; sudo apt update; sudo DEBIAN_FRONTEND=noninteractive apt install -y npm; npm install cspell", 20 | 21 | // Configure tool-specific properties. 22 | "customizations": { 23 | // Configure properties specific to VS Code. 24 | "vscode": { 25 | // Set *default* container specific settings.json values on container create. 26 | "settings": { 27 | "terminal.integrated.defaultProfile.linux": "pwsh" 28 | }, 29 | // Add the IDs of extensions you want installed when the container is created. 30 | "extensions": [ 31 | "EditorConfig.EditorConfig", 32 | "ms-vscode.powershell", 33 | "streetsidesoftware.code-spell-checker", 34 | "TylerLeonhardt.vscode-inline-values-powershell", 35 | "yzhang.markdown-all-in-one" 36 | ] 37 | } 38 | } 39 | 40 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 41 | // "forwardPorts": [], 42 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 43 | // "remoteUser": "root" 44 | } 45 | -------------------------------------------------------------------------------- /deploy/Invoke-SmokeTests.ps1: -------------------------------------------------------------------------------- 1 | # These tests are runs as part of the deployment process to ensure the newly published module is working as expected. 2 | # These tests run against the installed module, not the source code, so they are a real-world test and should not use mocks. 3 | # Since mocks are not used, be careful to not rely on state stored on the machine, such as a module configuration file. 4 | # This is a great place to put tests that differ between operating systems, since they will be ran on multiple platforms. 5 | # Keep in mind that these tests can only call the public functions in the module, not the private functions. 6 | # To run these tests on your local machine, see the comments in the BeforeAll block. 7 | 8 | BeforeAll { 9 | Import-Module -Name 'ScriptModuleRepositoryTemplate' -Force 10 | 11 | # To run these tests on your local machine, comment out the Import-Module command above and uncomment the one below. 12 | # Do this to use the module version from source code, not the installed version. 13 | # This is necessary to test functionality that you've added to the module, but have not yet published and installed. 14 | # Import-Module "$PSScriptRoot\..\src\ScriptModuleRepositoryTemplate" -Force 15 | } 16 | 17 | Describe 'New-PowerShellScriptModuleRepository' { 18 | It 'Should create a new directory with the module repository files' { 19 | # Arrange. 20 | $repositoryDirectoryPath = "$TestDrive\NewModuleRepo" 21 | $moduleName = 'NewModule' 22 | $organizationName = 'My Organization' 23 | 24 | $expectedModuleDirectoryPath = Join-Path -Path $repositoryDirectoryPath -ChildPath "src\$moduleName" 25 | $expectedModuleFilePath = Join-Path -Path $expectedModuleDirectoryPath -ChildPath "$moduleName.psm1" 26 | $expectedModuleManifestFilePath = Join-Path -Path $expectedModuleDirectoryPath -ChildPath "$moduleName.psd1" 27 | $expectedModuleTestsFilePath = Join-Path -Path $expectedModuleDirectoryPath -ChildPath "$moduleName.Tests.ps1" 28 | 29 | # Act. 30 | New-PowerShellScriptModuleRepository -RepositoryDirectoryPath $repositoryDirectoryPath -ModuleName $moduleName -OrganizationName $organizationName 31 | 32 | # Assert. 33 | $expectedModuleDirectoryPath | Should -Exist 34 | $expectedModuleFilePath | Should -Exist 35 | $expectedModuleManifestFilePath | Should -Exist 36 | $expectedModuleTestsFilePath | Should -Exist 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/ArchitectureDecisionRecords/001-Use-separate-repo-files-for-template.md: -------------------------------------------------------------------------------- 1 | # 001 - Use separate repo files for template 2 | 3 | ## Status 4 | 5 | Accepted 2024-02-25 6 | 7 | ## Context 8 | 9 | When I originally conceived of this template repository I was just going to have the template files be this actual repo's files, and I started out the implementation that way. 10 | When writing the script that would be used to initialize the template repository with the user's module information, I had to keep track of which files in the repo would need to be updated (e.g. the new module directory and files, License, Changelog, Smoke Tests). 11 | This had me questioning if it would be better to use totally separate files for all of the template files, and just have the initialization script overwrite all of the repo contents with the template files. 12 | 13 | ## Options Considered 14 | 15 | 1. Use this repo's files as the template files. 16 | 1. Use separate files for all of the template files (stored in their own directory). 17 | 18 | ### Use this repo's files as the template files 19 | 20 | Pros: 21 | 22 | - Fewer files in the repo. 23 | - Possibly less confusing to the user, as it follows the traditional idea of what a template repository is. 24 | e.g. the files in the repo are where you start from. 25 | 26 | ### Use separate files for all of the template files 27 | 28 | Pros: 29 | 30 | - Less logic in the initialization script, as we don't need to keep track of which files are for the repo and which are for the template. 31 | - Easier to update the template files without having to worry about the repo files. 32 | - Less confusion about which files are for this repo vs. which are for the template. 33 | - Allows users to more easily create their own custom template files easier if they want to; they just need to update the template files. 34 | - Allows for the PowerShell module to be able to create new starter repos, rather than having to clone the GitHub repo template. 35 | 36 | Cons: 37 | 38 | - Some of the files in the repo will be duplicates of the template files, so when adding new feature/changes we may need to remember to make them in both this repo and the template files. 39 | 40 | ## Decision 41 | 42 | We are going to store all of the template repo files in their own directory, as this separation makes it easier to understand which files are part of the template and which are part of this repo. 43 | It also allows for users to more easily create their own custom templates, and to use the PowerShell module to create new starter repos. 44 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/ScriptModuleRepositoryTemplate.Tests.ps1: -------------------------------------------------------------------------------- 1 | using module '.\ScriptModuleRepositoryTemplate.psm1' 2 | 3 | Describe 'New-PowerShellScriptModuleRepository' { 4 | BeforeEach { 5 | [string] $TemporaryRepoPath = "$TestDrive\NewModule" 6 | if (Test-Path -Path $TemporaryRepoPath) { 7 | Remove-Item -Path $TemporaryRepoPath -Recurse -Force 8 | } 9 | } 10 | 11 | It 'Should create a new directory with the module repository files using the specified module name' { 12 | # Arrange. 13 | $repositoryDirectoryPath = $TemporaryRepoPath 14 | $moduleName = 'NewModule' 15 | $organizationName = 'My Organization' 16 | 17 | $expectedModuleDirectoryPath = Join-Path -Path $repositoryDirectoryPath -ChildPath "src\$moduleName" 18 | $expectedModuleFilePath = Join-Path -Path $expectedModuleDirectoryPath -ChildPath "$moduleName.psm1" 19 | $expectedModuleManifestFilePath = Join-Path -Path $expectedModuleDirectoryPath -ChildPath "$moduleName.psd1" 20 | $expectedModuleTestsFilePath = Join-Path -Path $expectedModuleDirectoryPath -ChildPath "$moduleName.Tests.ps1" 21 | 22 | # Act. 23 | New-PowerShellScriptModuleRepository -RepositoryDirectoryPath $repositoryDirectoryPath -ModuleName $moduleName -OrganizationName $organizationName 24 | 25 | # Assert. 26 | $expectedModuleDirectoryPath | Should -Exist 27 | $expectedModuleFilePath | Should -Exist 28 | $expectedModuleManifestFilePath | Should -Exist 29 | $expectedModuleTestsFilePath | Should -Exist 30 | } 31 | 32 | It 'Should replace all dot-files and dot-directories prefixed with an underscore to remove the underscore' { 33 | # Arrange. 34 | $repositoryDirectoryPath = $TemporaryRepoPath 35 | $moduleName = 'NewModule' 36 | $organizationName = 'My Organization' 37 | 38 | $expectedDotDirectoryPath = Join-Path -Path $repositoryDirectoryPath -ChildPath ".vscode" 39 | $expectedDotFilePath = Join-Path -Path $repositoryDirectoryPath -ChildPath ".gitignore" 40 | 41 | # Act. 42 | New-PowerShellScriptModuleRepository -RepositoryDirectoryPath $repositoryDirectoryPath -ModuleName $moduleName -OrganizationName $organizationName 43 | 44 | # Assert. 45 | 46 | # No files should start with '_.' 47 | $repoFilePaths = Get-ChildItem -Path $repositoryDirectoryPath -Recurse -Force 48 | $repoFilePaths | ForEach-Object { 49 | [string] $fileName = $_.Name 50 | $fileName | Should -Not -Match '^_\.' 51 | } 52 | 53 | # Verify at least one Dot directory and file were renamed properly. 54 | $expectedDotDirectoryPath | Should -Exist 55 | $expectedDotFilePath | Should -Exist 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /docs/ArchitectureDecisionRecords/ArchitectureDecisionRecords.md: -------------------------------------------------------------------------------- 1 | # Architecture Decision Records (ADR) 2 | 3 | Here is where we log decisions we make about the architecture of the project. 4 | Each decision will have it's own dedicated ADR file in this directory. 5 | 6 | See the [Architecture Decision Record](https://github.com/joelparkerhenderson/architecture-decision-record) for more details of what an ADR is and common formats for it. 7 | 8 | ## Document format 9 | 10 | The ADR file name should be prefixed with an incrementing, sequential number. 11 | This shows the order in which decisions were made over the lifetime of the project. 12 | e.g. `001-Use-ADRs-for-decisions.md`. 13 | 14 | Here is an example of the sections that an ADR typically includes: 15 | 16 | ```markdown 17 | ## Status 18 | 19 | What is the status, such as _proposed_, _accepted_, _rejected_, _superseded_, etc.? If _superseded_ by a subsequent decision, link to the subsequent decision. Include the date that the status change was made. 20 | 21 | ## Context 22 | 23 | What is the issue that we're seeing that is motivating this decision or change? Are there any social or budgetary concerns that must be factored into the decision? Hyperlinks to supporting documentation are encouraged. 24 | 25 | ## Options Considered 26 | 27 | What options are available to solve the aforementioned issue? What are the tradeoffs associated with each option? Hyperlinks to supporting documentation are encouraged. 28 | 29 | ## Decision 30 | 31 | What is the change that we're proposing and/or doing to solve the aforementioned issue? 32 | 33 | ## Consequences 34 | 35 | What becomes easier or more difficult to do because of this change? What are the immediate action items? 36 | 37 | ## References / Additional Reading 38 | 39 | What individuals, teams, vendor documentation, and/or articles were consulted when gathering information throughout the decision-making process? 40 | ``` 41 | 42 | And an example of what an ADR might actually look like: 43 | 44 | ```markdown 45 | # 001 - Record Architecture Decisions 46 | 47 | ## Status 48 | 49 | Accepted 2022-10-28 50 | 51 | ## Context 52 | 53 | As the project is an example of a more advanced monolith architecture, it is necessary to save all architectural decisions in one place. 54 | 55 | ## Decision 56 | 57 | For all architectural decisions Architecture Decision Log (ADL) is created. All decisions will be recorded as Architecture Decision Records (ADR). 58 | 59 | Each ADR will be recorded in the repository and contain following sections: __Status__, __Context__, __Options Considered__ (optional if decision is straightforward), __Decision__, __Consequences__, and __References / Additional Reading__ (optional, but strongly encouraged). 60 | 61 | ## Consequences 62 | 63 | - All significant architectural decisions shall be recorded. 64 | - Old decisions should be recorded with an approximate decision date. 65 | - New decisions shall be recorded on a regular basis. 66 | ``` 67 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more info about out the tasks.json format see: https://code.visualstudio.com/docs/editor/tasks 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "label": "Run all build tasks", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | }, 11 | "dependsOn": [ 12 | "Run PSScriptAnalyzer linter" 13 | ] 14 | }, 15 | { 16 | "label": "Run all test tasks", 17 | "group": { 18 | "kind": "test", 19 | "isDefault": true 20 | }, 21 | "dependsOn": [ 22 | "Run all Pester tests" 23 | ] 24 | }, 25 | { 26 | "label": "Run PSScriptAnalyzer linter", 27 | "type": "shell", 28 | "options": { 29 | "shell": { 30 | "executable": "pwsh", 31 | "args": [ 32 | "-NoProfile", 33 | "-Command" 34 | ] 35 | } 36 | }, 37 | "command": "Invoke-ScriptAnalyzer -Path . -Recurse -EnableExit", 38 | "group": "build", 39 | "presentation": { 40 | "reveal": "always", 41 | "panel": "dedicated", 42 | "clear": true, 43 | "group": "build" 44 | }, 45 | "problemMatcher": [ 46 | "$func-powershell-watch" 47 | ] 48 | }, 49 | { 50 | "label": "Run all Pester tests", 51 | "type": "shell", 52 | "options": { 53 | "shell": { 54 | "executable": "pwsh", 55 | "args": [ 56 | "-NoProfile", 57 | "-Command" 58 | ] 59 | } 60 | }, 61 | "command": "Invoke-Pester -Configuration (New-PesterConfiguration @{ Output = @{ Verbosity = 'Detailed' }})", 62 | "group": "test", 63 | "presentation": { 64 | "reveal": "always", 65 | "panel": "dedicated", 66 | "clear": true 67 | }, 68 | "problemMatcher": [ 69 | "$func-powershell-watch" 70 | ] 71 | }, 72 | { 73 | "label": "Run Pester code coverage", 74 | "type": "shell", 75 | "options": { 76 | "shell": { 77 | "executable": "pwsh", 78 | "args": [ 79 | "-NoProfile", 80 | "-Command" 81 | ] 82 | } 83 | }, 84 | "command": "Invoke-Pester -Configuration (New-PesterConfiguration @{ Output = @{ Verbosity = 'Detailed' }; CodeCoverage = @{ Enabled = $true }})", 85 | "group": "test", 86 | "presentation": { 87 | "reveal": "always", 88 | "panel": "dedicated", 89 | "clear": true 90 | }, 91 | "problemMatcher": [ 92 | "$func-powershell-watch" 93 | ] 94 | }, 95 | { 96 | "label": "Test module manifest validity", 97 | "type": "shell", 98 | "options": { 99 | "shell": { 100 | "executable": "pwsh", 101 | "args": [ 102 | "-NoProfile", 103 | "-Command" 104 | ] 105 | } 106 | }, 107 | "command": "Get-ChildItem -Path ./src -Include *.psd1 -Recurse | Test-ModuleManifest", 108 | "group": "test", 109 | "presentation": { 110 | "reveal": "always", 111 | "panel": "dedicated", 112 | "clear": true 113 | }, 114 | "problemMatcher": [ 115 | "$func-powershell-watch" 116 | ] 117 | } 118 | ] 119 | } 120 | -------------------------------------------------------------------------------- /docs/Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | Feel free to open an issue or pull request. 4 | 5 | ## 🚀 Publishing new versions 6 | 7 | This project acts as a template repository in GitHub, meaning that as soon as a change is pushed to the `main` branch, it will be used when new GitHub repositories are created from this template. 8 | 9 | This project also creates a PowerShell module that can be used to create new repositories. 10 | A prerelease version of the module is published to the PowerShell Gallery automatically on every commit to the `main` branch. 11 | The [GitHub Actions `deploy` workflow run](https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/actions/workflows/build-test-and-deploy-powershell-module.yml) must be manually approved to publish a stable version. 12 | 13 | ### Incrementing the version number 14 | 15 | The version number is of the format Major.Minor.Patch and follows [semantic versioning](https://semver.org). 16 | By default, every commit to the `main` branch will increment the Patch version number. 17 | 18 | If you want to increment the Major or Minor version number, you have 2 options: 19 | 20 | 1. Manually start a [`deploy` workflow](https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/actions/workflows/build-test-and-deploy-powershell-module.yml) run and specify the version number to use. 21 | e.g. Specifying `2.4.0` will produce a new version of `2.4.0`. 22 | 1. Create a new version tag and pushing it up to GitHub. 23 | Builds are not triggered on tags, and thus the version tag will be used as the starting point for the next version. 24 | e.g. Creating a new tag of `v2.4.0` will produce a new version of `2.4.1` on the next commit to the `main` branch. 25 | 26 | ## 📄 Why are the template dot-files filenames prefixed with an underscore? 27 | 28 | `Publish-Module` has a bug where it does not include any files or directories starting with `.` in the module NuGet package. 29 | The newer `Publish-PSResource` has fixed this issue somewhat so the directories and some of the files are included, but it still leaves out some dot-files, like the `.gitignore` and `.editorconfig` files. 30 | To work around these issues, we prefix the files with an underscore (e.g. `_.gitignore`) so that they are included in the module package, and then remove the underscore prefix during the file copy process of the `New-PowerShellScriptModuleRepository` cmdlet. 31 | 32 | ## 🧪 Smoke tests 33 | 34 | [The Smoke tests](/deploy/Invoke-SmokeTests.ps1) are used during the CI/CD workflow to verify that the module is working as expected after it is published to the gallery. 35 | The smoke tests are ran on Windows, MacOS, and Linux agents to ensure cross-platform compatibility, as well as against a Windows PowerShell 5.1 agent to ensure backward compatibility. 36 | 37 | The difference between the smoke tests and the regular tests is that the smoke tests rely on the module actually being installed, not just the files being present. 38 | This means they can only test the functions and aliases that are exported from the module manifest and publicly accessible. 39 | This is the reason why we must use different files for the smoke tests than the regular tests; regular tests can test private functions and variables, but smoke tests cannot. 40 | 41 | ## ⁉ Why was a specific decision made 42 | 43 | Curious about some of the choices made in this project? 44 | The reasons may be documented in the [Architecture Decision Records](/docs/ArchitectureDecisionRecords/). 45 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/docs/Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | Feel free to open an issue or pull request. 4 | 5 | ## 💻 Local development 6 | 7 | This PowerShell module is developed using Visual Studio Code. 8 | If you encounter any issues developing on your local machine, you can use [Docker Desktop](https://www.docker.com/products/docker-desktop/) and the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) VS Code extension to develop in a Docker container with all of the required dependencies, so that you do not have to install them locally. 9 | You may also develop in your web browser with GitHub Codespaces to avoid needing to put any code or dependencies on your local machine. 10 | 11 | ### 🛠️ Building and testing 12 | 13 | The code is built and tested by CI/CD pipelines on every commit to the `main` branch and every PR opened against the `main` branch. 14 | 15 | When developing locally, you can use [the VS Code tasks](/.vscode/tasks.json) to simulate the build and test process and be notified of any problems before pushing your code up to the remote repository. 16 | In VS Code, open the command palette (Ctrl+Shift+P) and select `Tasks: Run Build Task` or `Tasks: Run Test Task`. 17 | 18 | When you run the build task, it will run PSScriptAnalyzer and CSpell spellcheck. 19 | If CSpell flags a word as `unknown` that is not misspelled, you can add it to the `.cspell.json` file in the root of the repository to have it ignore the word. 20 | 21 | ## 🚀 Publishing new versions 22 | 23 | A prerelease version of the module is published automatically on every commit to the `main` branch. 24 | The [GitHub Actions `deploy` workflow](/.github/workflows/build-test-and-deploy-powershell-module.yml) run must be manually approved to publish a stable version. 25 | 26 | ### Incrementing the version number 27 | 28 | The version number is of the format Major.Minor.Patch and follows [semantic versioning](https://semver.org). 29 | By default, every commit to the `main` branch will increment the Patch version number. 30 | 31 | If you want to increment the Major or Minor version number, you have 2 options: 32 | 33 | 1. Manually start a [`deploy` workflow](/.github/workflows/build-test-and-deploy-powershell-module.yml) run and specify the version number to use. 34 | e.g. Specifying `2.4.0` will produce a new version of `2.4.0`. 35 | 1. Create a new version tag and pushing it up to GitHub. 36 | Builds are not triggered on tags, and thus the version tag will be used as the starting point for the next version. 37 | e.g. Creating a new tag of `v2.4.0` will produce a new version of `2.4.1` on the next commit to the `main` branch. 38 | 39 | ## 🧪 Smoke tests 40 | 41 | [The Smoke tests](/deploy/Invoke-SmokeTests.ps1) are used during the CI/CD workflow to verify that the module is working as expected after it is published to the gallery. 42 | The smoke tests are ran on Windows, MacOS, and Linux agents to ensure cross-platform compatibility, as well as against a Windows PowerShell 5.1 agent to ensure backward compatibility. 43 | 44 | The difference between the smoke tests and the regular tests is that the smoke tests rely on the module actually being installed, not just the files being present. 45 | This means they can only test the functions and aliases that are exported from the module manifest and publicly accessible. 46 | This is the reason why we must use different files for the smoke tests than the regular tests; regular tests can test private functions and variables, but smoke tests cannot. 47 | 48 | You do not need to run every public function test in the smoke tests, but it can help give you confidence that the module is truly cross-platform and backward compatible. 49 | If you do not want to run any smoke tests, you can comment out all of the code in the smoke tests file. 50 | The CI/CD workflow will still validate that the module can be installed on each platform. 51 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more info about out the tasks.json format see: https://code.visualstudio.com/docs/editor/tasks 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "label": "Run all build tasks", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | }, 11 | "dependsOn": [ 12 | "Run PSScriptAnalyzer linter", 13 | "Run CSpell spell checker" 14 | ] 15 | }, 16 | { 17 | "label": "Run all test tasks", 18 | "group": { 19 | "kind": "test", 20 | "isDefault": true 21 | }, 22 | "dependsOn": [ 23 | "Run all Pester tests" 24 | ] 25 | }, 26 | { 27 | "label": "Run PSScriptAnalyzer linter", 28 | "type": "shell", 29 | "options": { 30 | "shell": { 31 | "executable": "pwsh", 32 | "args": [ 33 | "-NoProfile", 34 | "-Command" 35 | ] 36 | } 37 | }, 38 | "command": "Invoke-ScriptAnalyzer -Path . -Recurse -EnableExit", 39 | "group": "build", 40 | "presentation": { 41 | "reveal": "always", 42 | "panel": "dedicated", 43 | "clear": true, 44 | "group": "build" 45 | }, 46 | "problemMatcher": [ 47 | "$func-powershell-watch" 48 | ] 49 | }, 50 | { 51 | "label": "Run CSpell spell checker", 52 | "type": "shell", 53 | "options": { 54 | "shell": { 55 | "executable": "pwsh", 56 | "args": [ 57 | "-NoProfile", 58 | "-Command" 59 | ] 60 | } 61 | }, 62 | // If npx is not available, warn that Node.js is not installed. If we cannot run cspell, try to install and run it, and warn if we still cannot run it. 63 | "command": "try { & npx -v > $null } catch {}; if (-not $?) { Write-Warning 'Node.js is not installed, so cannot download and run npx cspell.' } else { try { & npx cspell . } catch {}; if (-not $?) { & npm install cspell; & npx cspell . }; if (-not $?) { Write-Warning 'There was a problem installing or running cspell' } }", 64 | "group": "build", 65 | "presentation": { 66 | "reveal": "always", 67 | "panel": "dedicated", 68 | "clear": true, 69 | "group": "build" 70 | }, 71 | "problemMatcher": [ 72 | "$func-powershell-watch" 73 | ] 74 | }, 75 | { 76 | "label": "Run all Pester tests", 77 | "type": "shell", 78 | "options": { 79 | "shell": { 80 | "executable": "pwsh", 81 | "args": [ 82 | "-NoProfile", 83 | "-Command" 84 | ] 85 | } 86 | }, 87 | "command": "Invoke-Pester -Configuration (New-PesterConfiguration @{ Output = @{ Verbosity = 'Detailed' }})", 88 | "group": "test", 89 | "presentation": { 90 | "reveal": "always", 91 | "panel": "dedicated", 92 | "clear": true 93 | }, 94 | "problemMatcher": [ 95 | "$func-powershell-watch" 96 | ] 97 | }, 98 | { 99 | "label": "Run Pester code coverage", 100 | "type": "shell", 101 | "options": { 102 | "shell": { 103 | "executable": "pwsh", 104 | "args": [ 105 | "-NoProfile", 106 | "-Command" 107 | ] 108 | } 109 | }, 110 | "command": "Invoke-Pester -Configuration (New-PesterConfiguration @{ Output = @{ Verbosity = 'Detailed' }; CodeCoverage = @{ Enabled = $true }})", 111 | "group": "test", 112 | "presentation": { 113 | "reveal": "always", 114 | "panel": "dedicated", 115 | "clear": true 116 | }, 117 | "problemMatcher": [ 118 | "$func-powershell-watch" 119 | ] 120 | }, 121 | { 122 | "label": "Test module manifest validity", 123 | "type": "shell", 124 | "options": { 125 | "shell": { 126 | "executable": "pwsh", 127 | "args": [ 128 | "-NoProfile", 129 | "-Command" 130 | ] 131 | } 132 | }, 133 | "command": "Get-ChildItem -Path ./src -Include *.psd1 -Recurse | Test-ModuleManifest", 134 | "group": "test", 135 | "presentation": { 136 | "reveal": "always", 137 | "panel": "dedicated", 138 | "clear": true 139 | }, 140 | "problemMatcher": [ 141 | "$func-powershell-watch" 142 | ] 143 | } 144 | ] 145 | } 146 | -------------------------------------------------------------------------------- /_InitializeRepository.ps1: -------------------------------------------------------------------------------- 1 | # Run this script to setup the repository for your module. 2 | 3 | [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')] 4 | Param() 5 | 6 | Process 7 | { 8 | Write-Host -ForegroundColor Yellow " 9 | This script will delete all files in this repo and replace them with template files specific to your module. 10 | If you have made changes to any files you may want to commit them before continuing, as this script will likely overwrite them. 11 | " 12 | 13 | [string] $moduleName = Read-Host -Prompt "Enter the name of your module without spaces (e.g. 'YourModuleName')" 14 | 15 | [string] $organizationName = Read-Host -Prompt "Enter your name, or the the name of your organization (e.g. 'My Company'). This will be used in the module manifest and repository license" 16 | 17 | Write-Verbose "Copying template repository module files to a temporary location to run it from." 18 | [string] $tempModuleDirectoryPath = CopyTemplateModuleFilesToTemporaryDirectory -templateModuleDirectoryPath $TemplateModuleDirectoryPath 19 | 20 | Write-Information "Removing all files from this repository so they can be replaced with template repository files." 21 | RemoveAllUnnecessaryRepositoryFiles -repositoryDirectoryPath $RepositoryDirectoryPath 22 | 23 | Write-Information "Creating the template repository files." 24 | Import-Module -Name $tempModuleDirectoryPath -Force 25 | New-PowerShellScriptModuleRepository -RepositoryDirectoryPath $RepositoryDirectoryPath -ModuleName $moduleName -OrganizationName $organizationName 26 | Remove-Module -Name $TemplateModuleName -Force 27 | 28 | Write-Verbose "Removing the temporary template module files since we are done using it to create the template repository files." 29 | RemoveTemporaryModuleDirectory -tempModuleDirectoryPath $tempModuleDirectoryPath 30 | 31 | Write-Verbose "Deleting this script as it is no longer needed." 32 | DeleteThisScript 33 | 34 | Write-Host -ForegroundColor Green "Repo initialization complete. You can now commit the changes to your repository." 35 | } 36 | 37 | Begin 38 | { 39 | $InformationPreference = 'Continue' 40 | [string] $RepositoryDirectoryPath = Resolve-Path -Path $PSScriptRoot 41 | [string] $TemplateModuleName = 'ScriptModuleRepositoryTemplate' 42 | [string] $TemplateModuleDirectoryPath = "$RepositoryDirectoryPath\src\$TemplateModuleName" 43 | 44 | function CopyTemplateModuleFilesToTemporaryDirectory([string] $templateModuleDirectoryPath) 45 | { 46 | [string] $templateModuleName = Split-Path -Path $templateModuleDirectoryPath -Leaf 47 | [string] $tempModuleDirectoryPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), (New-Guid).Guid, $templateModuleName) 48 | Copy-Item -Path $templateModuleDirectoryPath -Destination $tempModuleDirectoryPath -Recurse -Force 49 | return $tempModuleDirectoryPath 50 | } 51 | 52 | function RemoveAllUnnecessaryRepositoryFiles([string] $repositoryDirectoryPath) 53 | { 54 | # Delete all files except the ones we want to keep. 55 | Get-ChildItem -Path $repositoryDirectoryPath -Recurse -File | 56 | Where-Object { 57 | $_.FullName -notlike "$repositoryDirectoryPath\.git\*" -and # Don't delete the .git directory. 58 | $_.FullName -notlike "$repositoryDirectoryPath\_InitializeRepository.ps1" # Don't delete this script. 59 | } | 60 | Remove-Item -Force 61 | 62 | # Delete all empty directories that were left behind. 63 | Get-ChildItem -Path $repositoryDirectoryPath -Recurse -Force -Directory | 64 | Sort-Object -Property FullName -Descending | # Delete child directories before parent directories. 65 | Where-Object { $_.GetFileSystemInfos().Count -eq 0 } | 66 | Remove-Item -Force 67 | } 68 | 69 | function RemoveTemporaryModuleDirectory([string] $tempModuleDirectoryPath) 70 | { 71 | if (Test-Path -Path $tempModuleDirectoryPath -PathType Container) 72 | { 73 | Remove-Item -Path $tempModuleDirectoryPath -Recurse -Force -ErrorAction SilentlyContinue 74 | } 75 | } 76 | 77 | function DeleteThisScript 78 | { 79 | [string] $scriptPath = Join-Path -Path $PSScriptRoot -ChildPath '_InitializeRepository.ps1' 80 | Remove-Item -Path $scriptPath -Force 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: File a bug/issue 3 | title: "Bug: <title>" 4 | labels: ["bug"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue already exists for the bug you encountered. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Summary 16 | description: A concise description of the problem you're experiencing. 17 | placeholder: | 18 | When I do '...', I expect '...' to happen, but instead '...' happens. 19 | validations: 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: Steps To Reproduce 24 | description: Steps to reproduce the behavior. 25 | placeholder: | 26 | 1. Open PowerShell v7 prompt as admin... 27 | 2. Navigate to the repo directory... 28 | 3. Run the command '...' 29 | 4. See error... 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: powershell-version 34 | attributes: 35 | label: PowerShell Version 36 | description: Paste verbatim output from `$PSVersionTable; $Host` below. **Please include `$Host`** so we know this version is from the Extension Terminal! 37 | render: console 38 | placeholder: | 39 | PS> $PSVersionTable; $Host 40 | 41 | Name Value 42 | ---- ----- 43 | PSVersion 7.4.0 44 | PSEdition Core 45 | GitCommitId 7.4.0 46 | OS Microsoft Windows 10.0.22631 47 | Platform Win32NT 48 | PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…} 49 | PSRemotingProtocolVersion 2.3 50 | SerializationVersion 1.1.0.1 51 | WSManStackVersion 3.0 52 | 53 | Name : Visual Studio Code Host 54 | Version : 2023.11.0 55 | InstanceId : 803ce61b-6187-4574-9c1f-827ebb11b8b6 56 | UI : System.Management.Automation.Internal.Host.InternalHostUserInterface 57 | CurrentCulture : en-US 58 | CurrentUICulture : en-US 59 | PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy 60 | DebuggerEnabled : True 61 | IsRunspacePushed : False 62 | Runspace : System.Management.Automation.Runspaces.LocalRunspace 63 | validations: 64 | required: true 65 | - type: textarea 66 | id: vscode-version 67 | attributes: 68 | label: Visual Studio Code Version 69 | description: Paste verbatim output from `code --version` below. 70 | render: console 71 | placeholder: | 72 | PS> code --version 73 | 74 | 1.57.1 75 | 507ce72a4466fbb27b715c3722558bb15afa9f48 76 | arm64 77 | validations: 78 | required: true 79 | - type: textarea 80 | id: extension-version 81 | attributes: 82 | label: Extension Version 83 | description: Paste verbatim output from `code --list-extensions --show-versions | Select-String powershell` below. 84 | render: console 85 | placeholder: | 86 | PS> code --list-extensions --show-versions | Select-String powershell 87 | 88 | ms-vscode.powershell@2021.8.0 89 | validations: 90 | required: true 91 | - type: textarea 92 | attributes: 93 | label: Visuals 94 | description: Add screenshots, gifs, or videos to help explain the problem. 95 | placeholder: | 96 | Here is a screenshot of the problem after I do '...': 97 | ![Screenshot](https://example.com/screenshot.png) 98 | 99 | Here is a screenshot of the expected behaviour using an older version: 100 | ![Screenshot](https://example.com/screenshot-previous-version.png) 101 | validations: 102 | required: false 103 | - type: textarea 104 | attributes: 105 | label: Anything else? 106 | description: | 107 | Any other context or information about the problem or how to reproduce it. 108 | e.g. Logs files (with sensitive information removed), error messages, links, references, etc. 109 | 110 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 111 | validations: 112 | required: false 113 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: File a bug/issue 3 | title: "Bug: <title>" 4 | labels: ["bug"] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | description: Please search to see if an issue already exists for the bug you encountered. 10 | options: 11 | - label: I have searched the existing issues 12 | required: true 13 | - type: textarea 14 | attributes: 15 | label: Summary 16 | description: A concise description of the problem you're experiencing. 17 | placeholder: | 18 | When I do '...', I expect '...' to happen, but instead '...' happens. 19 | validations: 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: Steps To Reproduce 24 | description: Steps to reproduce the behavior. 25 | placeholder: | 26 | 1. Open PowerShell v7 prompt as admin... 27 | 2. Navigate to the repo directory... 28 | 3. Run the command '...' 29 | 4. See error... 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: powershell-version 34 | attributes: 35 | label: PowerShell Version 36 | description: Paste verbatim output from `$PSVersionTable; $Host` below. **Please include `$Host`** so we know this version is from the Extension Terminal! 37 | render: console 38 | placeholder: | 39 | PS> $PSVersionTable; $Host 40 | 41 | Name Value 42 | ---- ----- 43 | PSVersion 7.4.0 44 | PSEdition Core 45 | GitCommitId 7.4.0 46 | OS Microsoft Windows 10.0.22631 47 | Platform Win32NT 48 | PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…} 49 | PSRemotingProtocolVersion 2.3 50 | SerializationVersion 1.1.0.1 51 | WSManStackVersion 3.0 52 | 53 | Name : Visual Studio Code Host 54 | Version : 2023.11.0 55 | InstanceId : 803ce61b-6187-4574-9c1f-827ebb11b8b6 56 | UI : System.Management.Automation.Internal.Host.InternalHostUserInterface 57 | CurrentCulture : en-US 58 | CurrentUICulture : en-US 59 | PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy 60 | DebuggerEnabled : True 61 | IsRunspacePushed : False 62 | Runspace : System.Management.Automation.Runspaces.LocalRunspace 63 | validations: 64 | required: true 65 | - type: textarea 66 | id: vscode-version 67 | attributes: 68 | label: Visual Studio Code Version 69 | description: Paste verbatim output from `code --version` below. 70 | render: console 71 | placeholder: | 72 | PS> code --version 73 | 74 | 1.57.1 75 | 507ce72a4466fbb27b715c3722558bb15afa9f48 76 | arm64 77 | validations: 78 | required: true 79 | - type: textarea 80 | id: extension-version 81 | attributes: 82 | label: Extension Version 83 | description: Paste verbatim output from `code --list-extensions --show-versions | Select-String powershell` below. 84 | render: console 85 | placeholder: | 86 | PS> code --list-extensions --show-versions | Select-String powershell 87 | 88 | ms-vscode.powershell@2021.8.0 89 | validations: 90 | required: true 91 | - type: textarea 92 | attributes: 93 | label: Visuals 94 | description: Add screenshots, gifs, or videos to help explain the problem. 95 | placeholder: | 96 | Here is a screenshot of the problem after I do '...': 97 | ![Screenshot](https://example.com/screenshot.png) 98 | 99 | Here is a screenshot of the expected behaviour using an older version: 100 | ![Screenshot](https://example.com/screenshot-previous-version.png) 101 | validations: 102 | required: false 103 | - type: textarea 104 | attributes: 105 | label: Anything else? 106 | description: | 107 | Any other context or information about the problem or how to reproduce it. 108 | e.g. Logs files (with sensitive information removed), error messages, links, references, etc. 109 | 110 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 111 | validations: 112 | required: false 113 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/src/__NewModuleName__/__NewModuleName__.psd1: -------------------------------------------------------------------------------- 1 | # Module manifest docs: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_module_manifests 2 | 3 | @{ 4 | 5 | # Script module or binary module file associated with this manifest. 6 | RootModule = '__NewModuleName__.psm1' 7 | 8 | # Version number of this module. This will be updated automatically by the build and deployment pipelines. 9 | ModuleVersion = '0.0.0' 10 | 11 | # Supported PSEditions 12 | # CompatiblePSEditions = @() 13 | 14 | # ID used to uniquely identify this module 15 | GUID = '__NewModuleGuid__' 16 | 17 | # Author of this module 18 | Author = '__IndividualOrOrganizationName__' 19 | 20 | # Company or vendor of this module 21 | CompanyName = '__IndividualOrOrganizationName__' 22 | 23 | # Copyright statement for this module 24 | Copyright = '(c) __IndividualOrOrganizationName__. All rights reserved.' 25 | 26 | # Description of the functionality provided by this module 27 | # TODO: Update the description and delete this TODO comment. 28 | Description = '__NewModuleName__ module.' 29 | 30 | # Minimum version of the PowerShell engine required by this module 31 | # PowerShellVersion = '' 32 | 33 | # Name of the PowerShell host required by this module 34 | # PowerShellHostName = '' 35 | 36 | # Minimum version of the PowerShell host required by this module 37 | # PowerShellHostVersion = '' 38 | 39 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 40 | # DotNetFrameworkVersion = '' 41 | 42 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 43 | # ClrVersion = '' 44 | 45 | # Processor architecture (None, X86, Amd64) required by this module 46 | # ProcessorArchitecture = '' 47 | 48 | # Modules that must be imported into the global environment prior to importing this module 49 | # RequiredModules = @() 50 | 51 | # Assemblies that must be loaded prior to importing this module 52 | # RequiredAssemblies = @() 53 | 54 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 55 | # ScriptsToProcess = @() 56 | 57 | # Type files (.ps1xml) to be loaded when importing this module 58 | # TypesToProcess = @() 59 | 60 | # Format files (.ps1xml) to be loaded when importing this module 61 | # FormatsToProcess = @() 62 | 63 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 64 | # NestedModules = @() 65 | 66 | # Functions to export from this module. For best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 67 | # TODO: Update the functions list and delete this TODO comment. 68 | FunctionsToExport = @( 69 | 'Get-HelloWorld' 70 | ) 71 | 72 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 73 | CmdletsToExport = @() 74 | 75 | # Variables to export from this module 76 | VariablesToExport = '*' 77 | 78 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 79 | AliasesToExport = @() 80 | 81 | # DSC resources to export from this module 82 | # DscResourcesToExport = @() 83 | 84 | # List of all modules packaged with this module 85 | # ModuleList = @() 86 | 87 | # List of all files packaged with this module 88 | # FileList = @() 89 | 90 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 91 | PrivateData = @{ 92 | 93 | PSData = @{ 94 | 95 | # Tags applied to this module. These help with module discovery in online galleries. 96 | # TODO: Add appropriate module tags and delete this TODO comment. 97 | # Tags = @() 98 | 99 | # A URL to the license for this module. 100 | # TODO: Add a link to the module license and delete this TODO comment. 101 | # LicenseUri = '' 102 | 103 | # A URL to the main website for this project. 104 | # TODO: Add a link to your git repository or website and delete this TODO comment. 105 | # ProjectUri = '' 106 | 107 | # A URL to an icon representing this module. 108 | # IconUri = '' 109 | 110 | # ReleaseNotes of this module 111 | # TODO: Add a link to the Changelog.md page in the git repository and delete this TODO comment. 112 | # ReleaseNotes = '' 113 | 114 | # Prerelease string of this module 115 | # Prerelease = '' 116 | 117 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 118 | # RequireLicenseAcceptance = $false 119 | 120 | # External dependent modules of this module 121 | # ExternalModuleDependencies = @() 122 | 123 | } # End of PSData hashtable 124 | 125 | } # End of PrivateData hashtable 126 | 127 | # HelpInfo URI of this module 128 | # HelpInfoURI = '' 129 | 130 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 131 | # DefaultCommandPrefix = '' 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/ScriptModuleRepositoryTemplate.psd1: -------------------------------------------------------------------------------- 1 | # Module manifest docs: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_module_manifests 2 | 3 | @{ 4 | 5 | # Script module or binary module file associated with this manifest. 6 | RootModule = 'ScriptModuleRepositoryTemplate.psm1' 7 | 8 | # Version number of this module. 9 | ModuleVersion = '0.0.0' 10 | 11 | # Supported PSEditions 12 | # CompatiblePSEditions = @() 13 | 14 | # ID used to uniquely identify this module 15 | GUID = '643e6cf9-a844-4a1e-9771-643226c06943' 16 | 17 | # Author of this module 18 | Author = 'Daniel Schroeder' 19 | 20 | # Company or vendor of this module 21 | CompanyName = 'Daniel Schroeder' 22 | 23 | # Copyright statement for this module 24 | Copyright = '(c) Daniel Schroeder. All rights reserved.' 25 | 26 | # Description of the functionality provided by this module 27 | Description = 'Create new PowerShell script module repositories quickly and easily with boilerplate files and CI/CD workflows already defined. See the project site for more information.' 28 | 29 | # Minimum version of the PowerShell engine required by this module 30 | # PowerShellVersion = '' 31 | 32 | # Name of the PowerShell host required by this module 33 | # PowerShellHostName = '' 34 | 35 | # Minimum version of the PowerShell host required by this module 36 | # PowerShellHostVersion = '' 37 | 38 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 39 | # DotNetFrameworkVersion = '' 40 | 41 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 42 | # ClrVersion = '' 43 | 44 | # Processor architecture (None, X86, Amd64) required by this module 45 | # ProcessorArchitecture = '' 46 | 47 | # Modules that must be imported into the global environment prior to importing this module 48 | # RequiredModules = @() 49 | 50 | # Assemblies that must be loaded prior to importing this module 51 | # RequiredAssemblies = @() 52 | 53 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 54 | # ScriptsToProcess = @() 55 | 56 | # Type files (.ps1xml) to be loaded when importing this module 57 | # TypesToProcess = @() 58 | 59 | # Format files (.ps1xml) to be loaded when importing this module 60 | # FormatsToProcess = @() 61 | 62 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 63 | # NestedModules = @() 64 | 65 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 66 | FunctionsToExport = @( 67 | 'New-PowerShellScriptModuleRepository' 68 | ) 69 | 70 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 71 | CmdletsToExport = @() 72 | 73 | # Variables to export from this module 74 | VariablesToExport = '*' 75 | 76 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 77 | AliasesToExport = @() 78 | 79 | # DSC resources to export from this module 80 | # DscResourcesToExport = @() 81 | 82 | # List of all modules packaged with this module 83 | # ModuleList = @() 84 | 85 | # List of all files packaged with this module 86 | # FileList = @() 87 | 88 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 89 | PrivateData = @{ 90 | 91 | PSData = @{ 92 | 93 | # Tags applied to this module. These help with module discovery in online galleries. 94 | Tags = @( 95 | 'PowerShell' 96 | 'Module' 97 | 'Repository' 98 | 'Template' 99 | 'ScriptModule' 100 | 'Repo' 101 | 'GitHub' 102 | 'Actions' 103 | 'AzureDevOps' 104 | 'DevOps' 105 | 'Pipelines' 106 | 'CICD' 107 | 'Deploy' 108 | 'Windows' 109 | 'MacOS' 110 | 'Linux' 111 | ) 112 | 113 | # A URL to the license for this module. 114 | LicenseUri = 'https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/blob/main/License.md' 115 | 116 | # A URL to the main website for this project. 117 | ProjectUri = 'https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate' 118 | 119 | # A URL to an icon representing this module. 120 | # IconUri = '' 121 | 122 | # ReleaseNotes of this module 123 | ReleaseNotes = 'Changelog: https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/blob/main/Changelog.md' 124 | 125 | # Prerelease string of this module 126 | # Prerelease = '' 127 | 128 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 129 | # RequireLicenseAcceptance = $false 130 | 131 | # External dependent modules of this module 132 | # ExternalModuleDependencies = @() 133 | 134 | } # End of PSData hashtable 135 | 136 | } # End of PrivateData hashtable 137 | 138 | # HelpInfo URI of this module 139 | # HelpInfoURI = '' 140 | 141 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 142 | # DefaultCommandPrefix = '' 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/ScriptModuleRepositoryTemplate.psm1: -------------------------------------------------------------------------------- 1 | function New-PowerShellScriptModuleRepository 2 | { 3 | <# 4 | .SYNOPSIS 5 | Creates a new PowerShell script module repository directory with boilerplate files and CI/CD workflows already defined. 6 | 7 | .DESCRIPTION 8 | This function creates a new PowerShell script module repository with boilerplate files and CI/CD workflows already defined. This allows you to create new PowerShell script modules quickly and easily. 9 | 10 | Once the directory is created, you can run `git init` in it to initialize it as a git repository, and then push it to your own git server. 11 | 12 | You will then need to follow the instructions in the ReadMe.md file to finish setting up the repository. 13 | 14 | .PARAMETER RepositoryDirectoryPath 15 | The path to the new directory that should be created for the module repository. 16 | 17 | .PARAMETER ModuleName 18 | The name of the module to create. 19 | 20 | .PARAMETER OrganizationName 21 | The name of the individual or organization that owns the module. 22 | 23 | .EXAMPLE 24 | PS> New-PowerShellScriptModuleRepository -RepositoryDirectoryPath 'C:\MyNewModule' -ModuleName 'MyNewModule' -OrganizationName 'My Name' 25 | 26 | Creates a new module repository at 'C:\MyNewModule' with the module name 'MyNewModule' and the organization name 'My Name'. 27 | 28 | .INPUTS 29 | None. You cannot pipe objects to New-PowerShellScriptModuleRepository. 30 | 31 | .OUTPUTS 32 | None. New-PowerShellScriptModuleRepository does not return any output. 33 | It creates a new directory with the module repository files. 34 | 35 | .LINK 36 | https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate 37 | #> 38 | [CmdletBinding(SupportsShouldProcess)] 39 | [Alias('New-PSRepository')] 40 | Param 41 | ( 42 | [Parameter(Mandatory = $true, HelpMessage = "The path to the new directory that should be created for the module repository.")] 43 | [ValidateNotNullOrEmpty()] 44 | [string] $RepositoryDirectoryPath, 45 | 46 | [Parameter(Mandatory = $true, HelpMessage = "The name of the module to create.")] 47 | [ValidateNotNullOrEmpty()] 48 | [string] $ModuleName, 49 | 50 | [Parameter(Mandatory = $true, HelpMessage = "The name of the individual or organization that owns the module.")] 51 | [ValidateNotNullOrEmpty()] 52 | [string] $OrganizationName 53 | ) 54 | 55 | CopyTemplateFilesToRepositoryRoot -repositoryDirectoryPath $RepositoryDirectoryPath 56 | SetModuleFileNames -repositoryDirectoryPath $RepositoryDirectoryPath -moduleName $ModuleName 57 | SetTemplateTokenValuesInAllRepositoryFiles -repositoryDirectoryPath $RepositoryDirectoryPath -moduleName $ModuleName -organizationName $OrganizationName 58 | } 59 | 60 | function CopyTemplateFilesToRepositoryRoot([string] $repositoryDirectoryPath) 61 | { 62 | if (-not (Test-Path -Path $repositoryDirectoryPath -PathType Container)) 63 | { 64 | Write-Verbose "Creating the repository directory '$repositoryDirectoryPath'." 65 | New-Item -Path $repositoryDirectoryPath -ItemType Directory > $null 66 | } 67 | 68 | [string] $templateModuleDirectoryPath = "$PSScriptRoot\TemplateRepoFiles" 69 | if (Test-Path -Path $templateModuleDirectoryPath -PathType Container) 70 | { 71 | Write-Verbose "Copying the template repository files from '$templateModuleDirectoryPath' to the repository directory '$repositoryDirectoryPath'." 72 | Copy-Item -Path $templateModuleDirectoryPath\* -Destination $repositoryDirectoryPath -Recurse -Force 73 | } 74 | 75 | # Rename all dot-files prefixed with an underscore to remove the underscore. 76 | # The underscore prefix is a workaround to a bug with Publish-Module and Publish-PSResource that exclude dot-files 77 | # and dot-directories from being included in the module package. 78 | $repoDotFiles = Get-ChildItem -Path $repositoryDirectoryPath -Recurse -Force -Filter '_.*' 79 | $repoDotFiles | ForEach-Object { 80 | [string] $filePath = $_.FullName 81 | [string] $parentDirectory = Split-Path -Path $filePath -Parent 82 | [string] $newFileName = $_.Name -replace '^_\.', '.' 83 | [string] $newFilePath = Join-Path -Path $parentDirectory -ChildPath $newFileName 84 | Move-Item -Path $filePath -Destination $newFilePath -Force 85 | } 86 | } 87 | 88 | function SetModuleFileNames([string] $repositoryDirectoryPath, [string] $moduleName) 89 | { 90 | [string] $moduleDirectoryPath = "$repositoryDirectoryPath\src\__NewModuleName__" 91 | [string] $moduleFilePath = "$moduleDirectoryPath\__NewModuleName__.psm1" 92 | [string] $moduleManifestFilePath = "$moduleDirectoryPath\__NewModuleName__.psd1" 93 | [string] $moduleTestsFilePath = "$moduleDirectoryPath\__NewModuleName__.Tests.ps1" 94 | 95 | if (Test-Path -Path $moduleFilePath -PathType Leaf) 96 | { 97 | Rename-Item -Path $moduleFilePath -NewName "$moduleName.psm1" -Force 98 | } 99 | 100 | if (Test-Path -Path $moduleManifestFilePath -PathType Leaf) 101 | { 102 | Rename-Item -Path $moduleManifestFilePath -NewName "$moduleName.psd1" -Force 103 | } 104 | 105 | if (Test-Path -Path $moduleTestsFilePath -PathType Leaf) 106 | { 107 | Rename-Item -Path $moduleTestsFilePath -NewName "$moduleName.Tests.ps1" -Force 108 | } 109 | 110 | # Rename the directory last. 111 | if (Test-Path -Path $moduleDirectoryPath -PathType Container) 112 | { 113 | Rename-Item -Path $moduleDirectoryPath -NewName $moduleName -Force 114 | } 115 | } 116 | 117 | function SetTemplateTokenValuesInAllRepositoryFiles([string] $repositoryDirectoryPath, [string] $moduleName, [string] $organizationName) 118 | { 119 | $repositoryFiles = Get-ChildItem -Path $repositoryDirectoryPath -Recurse -File 120 | foreach ($file in $repositoryFiles) 121 | { 122 | $filePath = $file.FullName 123 | $contents = Get-Content -Path $filePath 124 | $contents = $contents -replace '__NewModuleName__', $moduleName 125 | $contents = $contents -replace '__IndividualOrOrganizationName__', $organizationName 126 | $contents = $contents -replace '__NewModuleGuid__', (New-Guid).ToString() 127 | $contents = $contents -replace '__CurrentYear__', (Get-Date).Year 128 | Set-Content -Path $filePath -Value $contents 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/ReadMe.md: -------------------------------------------------------------------------------- 1 | # Finish setting up your repo 2 | 3 | > [!IMPORTANT] 4 | > Congratulations on initializing your repository! 🎉 5 | > 6 | > 🚧 You are not quite done yet though. 🚧 7 | > 8 | > Complete the following steps to finish setting up your repository. 9 | 10 | ## 📄 Template setup instructions 11 | 12 | Steps 1 and 2 were already performed to get this far. 13 | 14 | ### ➕ Step 3: Add your module (if applicable) 15 | 16 | If you already have the module, manifest, and tests written, replace the following files with your module's files: 17 | 18 | - [__NewModuleName__.psm1](/src/__NewModuleName__/__NewModuleName__.psm1) 19 | - [__NewModuleName__.psd1](/src/__NewModuleName__/__NewModuleName__.psd1) 20 | - [__NewModuleName__.Tests.ps1](/src/__NewModuleName__/__NewModuleName__.Tests.ps1) 21 | 22 | Otherwise, use these files as a starting point for your new module. 23 | 24 | #### Smoke tests 25 | 26 | When you change the module and tests, you will also need to update [the Smoke tests](/deploy/Invoke-SmokeTests.ps1), otherwise they may fail the CI/CD workflow. 27 | See the [Contributing docs](/docs/Contributing.md) for more information on smoke tests. 28 | 29 | ### 🚀 Step 4: Update your CI/CD workflows 30 | 31 | #### 🔑 Create a PowerShell Gallery API Key 32 | 33 | In order to publish the module to the gallery, you need to get an API key. 34 | If you already have an API key that you want to use, you can skip to the next step. 35 | 36 | > [!CAUTION] 37 | > It is considered best practice to use a different API key for each module you publish. 38 | > It reduces the impact scope if one of the API keys becomes compromised. 39 | 40 | If you will be publishing the module to a custom PowerShell Gallery feed, you will need to get an API key for that feed. Otherwise, follow the steps below. 41 | 42 | <details> 43 | <summary>Click to view steps to create a new API Key for the PowerShell Gallery...</summary> 44 | 45 | 1. Navigate to <https://www.powershellgallery.com/account/apikeys>, and login if necessary. 46 | 1. Click `Create` to create a new API key for this module. 47 | 1. For the `Key Name` it is a good idea to include the name of your module. 48 | e.g. `__NewModuleName__ module CI/CD pipeline` 49 | 1. Ensure the `Push new packages and package versions` scope is selected. 50 | 1. For the `Glob Pattern` enter the name of your module: `__NewModuleName__` 51 | 1. Click the `Create` button to create the API key. 52 | 1. Click the `Copy` button on the new API key to copy it to your clipboard, as you will need it for the next section. 53 | 54 | You may want to leave this page open in your browser until you have the API key saved in your repository secrets in the next section. 55 | 56 | </details> 57 | 58 | #### ▶ GitHub Actions and Azure DevOps Pipelines setup instructions 59 | 60 | Building and publishing the PowerShell module using GitHub Actions and Azure DevOps Pipelines are both supported. 61 | Follow the instructions for the CI/CD platform you plan to use. 62 | 63 | <details> 64 | <summary>Click to see GitHub setup instructions...</summary> 65 | 66 | If using GitHub Actions for your CI/CD workflows, perform the following steps to setup your API key as a repository secret: 67 | 68 | 1. Navigate to your GitHub repository in your browser. 69 | 1. Go to the `Settings` tab for your repository. 70 | 1. In the left-hand menu, in the `Security` section, click on `Secrets and variables` and select `Actions`. 71 | 1. In the `Secrets` tab click the `New repository secret` button. 72 | 1. Set the `Secret` value to the API key value that you copied in the previous section. 73 | 1. Set the `Name` to: `POWERSHELL_GALLERY_API_KEY` 74 | 1. Click the `Add secret` button to save the repository secret. 75 | 76 | If you do not provide a valid API key, you will get an error like the following in the `Publish prerelease PowerShell module` step of the deployment workflow: 77 | 78 | ```text 79 | Failed to publish module '<module name>': 'dotnet cli failed to nuget push Pushing <module name>.nupkg to '<url>'... 80 | PUT <url> Forbidden 81 | error: Response status code does not indicate success: 403 (The specified API key is invalid, has expired, or does not have permission to access the specified package.). 82 | ``` 83 | 84 | Next we want to create an Environment so that stable module versions require manual approval before being published to the gallery: 85 | 86 | 1. You should still be in the `Settings` section of your repository. 87 | 1. In the left-hand menu, in the `Code and automation` section, click on `Environments`. 88 | 1. Click the `New environment` button. 89 | 1. Set the `Name` to (all lowercase): `production` 90 | 1. Click the `Configure environment` button. 91 | 1. Check the `Required reviewers` checkbox and add the usernames of the people allowed to approve new stable version deployments. 92 | e.g. your GitHub username. 93 | 1. Click the `Save protection rules` button. 94 | 95 | If your GitHub account does not meet [the requirements to use `Environments`](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment), the `Environments` section or `Required reviewers` options may not be available. 96 | Deployments will still work, but they will not pause for manual approval and will automatically deploy the stable version directly after the prerelease version is published. 97 | You will instead need to add [the Manual Workflow Approval action](https://github.com/marketplace/actions/manual-workflow-approval) to [the deployment workflow](/.github/workflows/build-test-and-deploy-powershell-module.yml) to block deployments until they are approved. 98 | 99 | Finally, we will need to grant GitHub Actions permission to add git tags to the repository so it can keep track of the version number: 100 | 101 | 1. You should still be in the `Settings` section of your repository. 102 | 1. In the left-hand menu, in the `Code and automation` section, click on `Actions` and select `General`. 103 | 1. Scroll down to `Workflow permissions` and ensure `Read and write permissions` is selected. 104 | 1. Click the `Save` button for the Workflow permissions. 105 | 106 | If you do not do this you will get the following error in the `Set the new version tag` step of the deployment workflow: 107 | 108 | ```text 109 | fatal: unable to access 'https://github.com/<Author>/<Repo>/': The requested URL returned error: 403 110 | ``` 111 | 112 | </details> 113 | 114 | <details> 115 | <summary>Click to see Azure DevOps setup instructions...</summary> 116 | 117 | Coming soon. 118 | 119 | </details> 120 | 121 | ### ✔ Step 5: Review and update boilerplate repo files 122 | 123 | The following boilerplate git repository files should be reviewed and updated or removed as needed. 124 | 125 | - [__NewModuleName__.psd1](/src/__NewModuleName__/__NewModuleName__.psd1): Update the module manifest with your module's information, such as the Description and have the ProjectUri link to this repository, etc. 126 | - If using the default generated manifest, search for "TODO" to find the properties that are recommended to update. 127 | - [Changelog](/Changelog.md): If you don't plan to track a changelog, remove this file and it's reference from the ReadMe. 128 | - [License](/License.md): Update to match your module's license, and ensure it uses the correct name and year in the copyright. 129 | - [Contributing](/docs/Contributing.md): Update to match your module's contributing guidelines, or remove it. 130 | - [bug_report](/.github/ISSUE_TEMPLATE/bug_report.md), [feature_request](/.github/ISSUE_TEMPLATE/feature_request.md), [pull_request_template](/.github/pull_request_template.md): Update these GitHub templates as needed to meet your requirements, or remove them. 131 | - Build and deployment workflows: The workflows include extra steps that you may not want, such as spell check, code coverage, etc. 132 | Review the workflows and remove any steps that you don't want to include in your CI/CD pipeline. 133 | - [ReadMe](/ReadMe.md): Update this file with your module's information. 134 | Some example template content is provided below; fill it out, or remove it and write your own. 135 | 136 | > [!IMPORTANT] 137 | > If you've made it this far, your repository is now ready for use! 🎉 138 | > 139 | > You may delete this message and all of the file's content above it and commit any changes you've made. 140 | 141 | # __NewModuleName__ PowerShell Module 142 | 143 | ## 💬 Description 144 | 145 | A short description of what this project does. 146 | Include a link to the module in the gallery. 147 | 148 | ## ❓ Why this exists 149 | 150 | A short description of why this project exists. 151 | What use-case is it meant to solve? 152 | 153 | ## ✨ Features 154 | 155 | List the features of this project: 156 | 157 | - Feature 1 158 | - Feature 2 159 | 160 | ## 🚀 Quick start 161 | 162 | A quick guide on how to get started with this module, including installation and usage: 163 | 164 | - A link to the module in the PowerShell Gallery. 165 | - Code examples of installing and and using the module. 166 | - Links to wiki or other documentation. 167 | 168 | ## ➕ How to contribute 169 | 170 | Issues and Pull Requests are welcome. 171 | See [the Contributing page](docs/Contributing.md) for more details. 172 | 173 | ## 📃 Changelog 174 | 175 | See what's changed in the application over time by viewing [the changelog](Changelog.md). 176 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/powershell,visualstudiocode,visualstudio 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=powershell,visualstudiocode,visualstudio 3 | 4 | ### PowerShell ### 5 | # Exclude packaged modules 6 | *.zip 7 | 8 | # Exclude .NET assemblies from source 9 | *.dll 10 | 11 | ### VisualStudioCode ### 12 | .vscode/* 13 | !.vscode/settings.json 14 | !.vscode/tasks.json 15 | !.vscode/launch.json 16 | !.vscode/extensions.json 17 | !.vscode/*.code-snippets 18 | 19 | # Local History for Visual Studio Code 20 | .history/ 21 | 22 | # Built Visual Studio Code Extensions 23 | *.vsix 24 | 25 | ### VisualStudioCode Patch ### 26 | # Ignore all local history of files 27 | .history 28 | .ionide 29 | 30 | ### VisualStudio ### 31 | ## Ignore Visual Studio temporary files, build results, and 32 | ## files generated by popular Visual Studio add-ons. 33 | ## 34 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 35 | 36 | # User-specific files 37 | *.rsuser 38 | *.suo 39 | *.user 40 | *.userosscache 41 | *.sln.docstates 42 | 43 | # User-specific files (MonoDevelop/Xamarin Studio) 44 | *.userprefs 45 | 46 | # Mono auto generated files 47 | mono_crash.* 48 | 49 | # Build results 50 | [Dd]ebug/ 51 | [Dd]ebugPublic/ 52 | [Rr]elease/ 53 | [Rr]eleases/ 54 | x64/ 55 | x86/ 56 | [Ww][Ii][Nn]32/ 57 | [Aa][Rr][Mm]/ 58 | [Aa][Rr][Mm]64/ 59 | bld/ 60 | [Bb]in/ 61 | [Oo]bj/ 62 | [Ll]og/ 63 | [Ll]ogs/ 64 | 65 | # Visual Studio 2015/2017 cache/options directory 66 | .vs/ 67 | # Uncomment if you have tasks that create the project's static files in wwwroot 68 | #wwwroot/ 69 | 70 | # Visual Studio 2017 auto generated files 71 | Generated\ Files/ 72 | 73 | # MSTest test Results 74 | [Tt]est[Rr]esult*/ 75 | [Bb]uild[Ll]og.* 76 | 77 | # NUnit 78 | *.VisualState.xml 79 | TestResult.xml 80 | nunit-*.xml 81 | 82 | # Build Results of an ATL Project 83 | [Dd]ebugPS/ 84 | [Rr]eleasePS/ 85 | dlldata.c 86 | 87 | # Benchmark Results 88 | BenchmarkDotNet.Artifacts/ 89 | 90 | # .NET Core 91 | project.lock.json 92 | project.fragment.lock.json 93 | artifacts/ 94 | 95 | # ASP.NET Scaffolding 96 | ScaffoldingReadMe.txt 97 | 98 | # StyleCop 99 | StyleCopReport.xml 100 | 101 | # Files built by Visual Studio 102 | *_i.c 103 | *_p.c 104 | *_h.h 105 | *.ilk 106 | *.meta 107 | *.obj 108 | *.iobj 109 | *.pch 110 | *.pdb 111 | *.ipdb 112 | *.pgc 113 | *.pgd 114 | *.rsp 115 | *.sbr 116 | *.tlb 117 | *.tli 118 | *.tlh 119 | *.tmp 120 | *.tmp_proj 121 | *_wpftmp.csproj 122 | *.log 123 | *.tlog 124 | *.vspscc 125 | *.vssscc 126 | .builds 127 | *.pidb 128 | *.svclog 129 | *.scc 130 | 131 | # Chutzpah Test files 132 | _Chutzpah* 133 | 134 | # Visual C++ cache files 135 | ipch/ 136 | *.aps 137 | *.ncb 138 | *.opendb 139 | *.opensdf 140 | *.sdf 141 | *.cachefile 142 | *.VC.db 143 | *.VC.VC.opendb 144 | 145 | # Visual Studio profiler 146 | *.psess 147 | *.vsp 148 | *.vspx 149 | *.sap 150 | 151 | # Visual Studio Trace Files 152 | *.e2e 153 | 154 | # TFS 2012 Local Workspace 155 | $tf/ 156 | 157 | # Guidance Automation Toolkit 158 | *.gpState 159 | 160 | # ReSharper is a .NET coding add-in 161 | _ReSharper*/ 162 | *.[Rr]e[Ss]harper 163 | *.DotSettings.user 164 | 165 | # TeamCity is a build add-in 166 | _TeamCity* 167 | 168 | # DotCover is a Code Coverage Tool 169 | *.dotCover 170 | 171 | # AxoCover is a Code Coverage Tool 172 | .axoCover/* 173 | !.axoCover/settings.json 174 | 175 | # Coverlet is a free, cross platform Code Coverage Tool 176 | coverage*.json 177 | coverage*.xml 178 | coverage*.info 179 | 180 | # Visual Studio code coverage results 181 | *.coverage 182 | *.coveragexml 183 | 184 | # NCrunch 185 | _NCrunch_* 186 | .*crunch*.local.xml 187 | nCrunchTemp_* 188 | 189 | # MightyMoose 190 | *.mm.* 191 | AutoTest.Net/ 192 | 193 | # Web workbench (sass) 194 | .sass-cache/ 195 | 196 | # Installshield output folder 197 | [Ee]xpress/ 198 | 199 | # DocProject is a documentation generator add-in 200 | DocProject/buildhelp/ 201 | DocProject/Help/*.HxT 202 | DocProject/Help/*.HxC 203 | DocProject/Help/*.hhc 204 | DocProject/Help/*.hhk 205 | DocProject/Help/*.hhp 206 | DocProject/Help/Html2 207 | DocProject/Help/html 208 | 209 | # Click-Once directory 210 | publish/ 211 | 212 | # Publish Web Output 213 | *.[Pp]ublish.xml 214 | *.azurePubxml 215 | # Note: Comment the next line if you want to checkin your web deploy settings, 216 | # but database connection strings (with potential passwords) will be unencrypted 217 | *.pubxml 218 | *.publishproj 219 | 220 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 221 | # checkin your Azure Web App publish settings, but sensitive information contained 222 | # in these scripts will be unencrypted 223 | PublishScripts/ 224 | 225 | # NuGet Packages 226 | *.nupkg 227 | # NuGet Symbol Packages 228 | *.snupkg 229 | # The packages folder can be ignored because of Package Restore 230 | **/[Pp]ackages/* 231 | # except build/, which is used as an MSBuild target. 232 | !**/[Pp]ackages/build/ 233 | # Uncomment if necessary however generally it will be regenerated when needed 234 | #!**/[Pp]ackages/repositories.config 235 | # NuGet v3's project.json files produces more ignorable files 236 | *.nuget.props 237 | *.nuget.targets 238 | 239 | # Microsoft Azure Build Output 240 | csx/ 241 | *.build.csdef 242 | 243 | # Microsoft Azure Emulator 244 | ecf/ 245 | rcf/ 246 | 247 | # Windows Store app package directories and files 248 | AppPackages/ 249 | BundleArtifacts/ 250 | Package.StoreAssociation.xml 251 | _pkginfo.txt 252 | *.appx 253 | *.appxbundle 254 | *.appxupload 255 | 256 | # Visual Studio cache files 257 | # files ending in .cache can be ignored 258 | *.[Cc]ache 259 | # but keep track of directories ending in .cache 260 | !?*.[Cc]ache/ 261 | 262 | # Others 263 | ClientBin/ 264 | ~$* 265 | *~ 266 | *.dbmdl 267 | *.dbproj.schemaview 268 | *.jfm 269 | *.pfx 270 | *.publishsettings 271 | orleans.codegen.cs 272 | 273 | # Including strong name files can present a security risk 274 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 275 | #*.snk 276 | 277 | # Since there are multiple workflows, uncomment next line to ignore bower_components 278 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 279 | #bower_components/ 280 | 281 | # RIA/Silverlight projects 282 | Generated_Code/ 283 | 284 | # Backup & report files from converting an old project file 285 | # to a newer Visual Studio version. Backup files are not needed, 286 | # because we have git ;-) 287 | _UpgradeReport_Files/ 288 | Backup*/ 289 | UpgradeLog*.XML 290 | UpgradeLog*.htm 291 | ServiceFabricBackup/ 292 | *.rptproj.bak 293 | 294 | # SQL Server files 295 | *.mdf 296 | *.ldf 297 | *.ndf 298 | 299 | # Business Intelligence projects 300 | *.rdl.data 301 | *.bim.layout 302 | *.bim_*.settings 303 | *.rptproj.rsuser 304 | *- [Bb]ackup.rdl 305 | *- [Bb]ackup ([0-9]).rdl 306 | *- [Bb]ackup ([0-9][0-9]).rdl 307 | 308 | # Microsoft Fakes 309 | FakesAssemblies/ 310 | 311 | # GhostDoc plugin setting file 312 | *.GhostDoc.xml 313 | 314 | # Node.js Tools for Visual Studio 315 | .ntvs_analysis.dat 316 | node_modules/ 317 | 318 | # Visual Studio 6 build log 319 | *.plg 320 | 321 | # Visual Studio 6 workspace options file 322 | *.opt 323 | 324 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 325 | *.vbw 326 | 327 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 328 | *.vbp 329 | 330 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 331 | *.dsw 332 | *.dsp 333 | 334 | # Visual Studio 6 technical files 335 | 336 | # Visual Studio LightSwitch build output 337 | **/*.HTMLClient/GeneratedArtifacts 338 | **/*.DesktopClient/GeneratedArtifacts 339 | **/*.DesktopClient/ModelManifest.xml 340 | **/*.Server/GeneratedArtifacts 341 | **/*.Server/ModelManifest.xml 342 | _Pvt_Extensions 343 | 344 | # Paket dependency manager 345 | .paket/paket.exe 346 | paket-files/ 347 | 348 | # FAKE - F# Make 349 | .fake/ 350 | 351 | # CodeRush personal settings 352 | .cr/personal 353 | 354 | # Python Tools for Visual Studio (PTVS) 355 | __pycache__/ 356 | *.pyc 357 | 358 | # Cake - Uncomment if you are using it 359 | # tools/** 360 | # !tools/packages.config 361 | 362 | # Tabs Studio 363 | *.tss 364 | 365 | # Telerik's JustMock configuration file 366 | *.jmconfig 367 | 368 | # BizTalk build output 369 | *.btp.cs 370 | *.btm.cs 371 | *.odx.cs 372 | *.xsd.cs 373 | 374 | # OpenCover UI analysis results 375 | OpenCover/ 376 | 377 | # Azure Stream Analytics local run output 378 | ASALocalRun/ 379 | 380 | # MSBuild Binary and Structured Log 381 | *.binlog 382 | 383 | # NVidia Nsight GPU debugger configuration file 384 | *.nvuser 385 | 386 | # MFractors (Xamarin productivity tool) working folder 387 | .mfractor/ 388 | 389 | # Local History for Visual Studio 390 | .localhistory/ 391 | 392 | # Visual Studio History (VSHistory) files 393 | .vshistory/ 394 | 395 | # BeatPulse healthcheck temp database 396 | healthchecksdb 397 | 398 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 399 | MigrationBackup/ 400 | 401 | # Ionide (cross platform F# VS Code tools) working folder 402 | .ionide/ 403 | 404 | # Fody - auto-generated XML schema 405 | FodyWeavers.xsd 406 | 407 | # VS Code files for those working on multiple tools 408 | *.code-workspace 409 | 410 | # Local History for Visual Studio Code 411 | 412 | # Windows Installer files from build outputs 413 | *.cab 414 | *.msi 415 | *.msix 416 | *.msm 417 | *.msp 418 | 419 | # JetBrains Rider 420 | *.sln.iml 421 | 422 | ### VisualStudio Patch ### 423 | # Additional files built by Visual Studio 424 | 425 | # End of https://www.toptal.com/developers/gitignore/api/powershell,visualstudiocode,visualstudio 426 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom additions: 2 | 3 | # Ignore NPM files. 4 | package-lock.json 5 | package.json 6 | 7 | # Created by https://www.toptal.com/developers/gitignore/api/powershell,visualstudiocode,visualstudio 8 | # Edit at https://www.toptal.com/developers/gitignore?templates=powershell,visualstudiocode,visualstudio 9 | 10 | ### PowerShell ### 11 | # Exclude packaged modules 12 | *.zip 13 | 14 | # Exclude .NET assemblies from source 15 | *.dll 16 | 17 | ### VisualStudioCode ### 18 | .vscode/* 19 | !.vscode/settings.json 20 | !.vscode/tasks.json 21 | !.vscode/launch.json 22 | !.vscode/extensions.json 23 | !.vscode/*.code-snippets 24 | 25 | # Local History for Visual Studio Code 26 | .history/ 27 | 28 | # Built Visual Studio Code Extensions 29 | *.vsix 30 | 31 | ### VisualStudioCode Patch ### 32 | # Ignore all local history of files 33 | .history 34 | .ionide 35 | 36 | ### VisualStudio ### 37 | ## Ignore Visual Studio temporary files, build results, and 38 | ## files generated by popular Visual Studio add-ons. 39 | ## 40 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 41 | 42 | # User-specific files 43 | *.rsuser 44 | *.suo 45 | *.user 46 | *.userosscache 47 | *.sln.docstates 48 | 49 | # User-specific files (MonoDevelop/Xamarin Studio) 50 | *.userprefs 51 | 52 | # Mono auto generated files 53 | mono_crash.* 54 | 55 | # Build results 56 | [Dd]ebug/ 57 | [Dd]ebugPublic/ 58 | [Rr]elease/ 59 | [Rr]eleases/ 60 | x64/ 61 | x86/ 62 | [Ww][Ii][Nn]32/ 63 | [Aa][Rr][Mm]/ 64 | [Aa][Rr][Mm]64/ 65 | bld/ 66 | [Bb]in/ 67 | [Oo]bj/ 68 | [Ll]og/ 69 | [Ll]ogs/ 70 | 71 | # Visual Studio 2015/2017 cache/options directory 72 | .vs/ 73 | # Uncomment if you have tasks that create the project's static files in wwwroot 74 | #wwwroot/ 75 | 76 | # Visual Studio 2017 auto generated files 77 | Generated\ Files/ 78 | 79 | # MSTest test Results 80 | [Tt]est[Rr]esult*/ 81 | [Bb]uild[Ll]og.* 82 | 83 | # NUnit 84 | *.VisualState.xml 85 | TestResult.xml 86 | nunit-*.xml 87 | 88 | # Build Results of an ATL Project 89 | [Dd]ebugPS/ 90 | [Rr]eleasePS/ 91 | dlldata.c 92 | 93 | # Benchmark Results 94 | BenchmarkDotNet.Artifacts/ 95 | 96 | # .NET Core 97 | project.lock.json 98 | project.fragment.lock.json 99 | artifacts/ 100 | 101 | # ASP.NET Scaffolding 102 | ScaffoldingReadMe.txt 103 | 104 | # StyleCop 105 | StyleCopReport.xml 106 | 107 | # Files built by Visual Studio 108 | *_i.c 109 | *_p.c 110 | *_h.h 111 | *.ilk 112 | *.meta 113 | *.obj 114 | *.iobj 115 | *.pch 116 | *.pdb 117 | *.ipdb 118 | *.pgc 119 | *.pgd 120 | *.rsp 121 | *.sbr 122 | *.tlb 123 | *.tli 124 | *.tlh 125 | *.tmp 126 | *.tmp_proj 127 | *_wpftmp.csproj 128 | *.log 129 | *.tlog 130 | *.vspscc 131 | *.vssscc 132 | .builds 133 | *.pidb 134 | *.svclog 135 | *.scc 136 | 137 | # Chutzpah Test files 138 | _Chutzpah* 139 | 140 | # Visual C++ cache files 141 | ipch/ 142 | *.aps 143 | *.ncb 144 | *.opendb 145 | *.opensdf 146 | *.sdf 147 | *.cachefile 148 | *.VC.db 149 | *.VC.VC.opendb 150 | 151 | # Visual Studio profiler 152 | *.psess 153 | *.vsp 154 | *.vspx 155 | *.sap 156 | 157 | # Visual Studio Trace Files 158 | *.e2e 159 | 160 | # TFS 2012 Local Workspace 161 | $tf/ 162 | 163 | # Guidance Automation Toolkit 164 | *.gpState 165 | 166 | # ReSharper is a .NET coding add-in 167 | _ReSharper*/ 168 | *.[Rr]e[Ss]harper 169 | *.DotSettings.user 170 | 171 | # TeamCity is a build add-in 172 | _TeamCity* 173 | 174 | # DotCover is a Code Coverage Tool 175 | *.dotCover 176 | 177 | # AxoCover is a Code Coverage Tool 178 | .axoCover/* 179 | !.axoCover/settings.json 180 | 181 | # Coverlet is a free, cross platform Code Coverage Tool 182 | coverage*.json 183 | coverage*.xml 184 | coverage*.info 185 | 186 | # Visual Studio code coverage results 187 | *.coverage 188 | *.coveragexml 189 | 190 | # NCrunch 191 | _NCrunch_* 192 | .*crunch*.local.xml 193 | nCrunchTemp_* 194 | 195 | # MightyMoose 196 | *.mm.* 197 | AutoTest.Net/ 198 | 199 | # Web workbench (sass) 200 | .sass-cache/ 201 | 202 | # Installshield output folder 203 | [Ee]xpress/ 204 | 205 | # DocProject is a documentation generator add-in 206 | DocProject/buildhelp/ 207 | DocProject/Help/*.HxT 208 | DocProject/Help/*.HxC 209 | DocProject/Help/*.hhc 210 | DocProject/Help/*.hhk 211 | DocProject/Help/*.hhp 212 | DocProject/Help/Html2 213 | DocProject/Help/html 214 | 215 | # Click-Once directory 216 | publish/ 217 | 218 | # Publish Web Output 219 | *.[Pp]ublish.xml 220 | *.azurePubxml 221 | # Note: Comment the next line if you want to checkin your web deploy settings, 222 | # but database connection strings (with potential passwords) will be unencrypted 223 | *.pubxml 224 | *.publishproj 225 | 226 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 227 | # checkin your Azure Web App publish settings, but sensitive information contained 228 | # in these scripts will be unencrypted 229 | PublishScripts/ 230 | 231 | # NuGet Packages 232 | *.nupkg 233 | # NuGet Symbol Packages 234 | *.snupkg 235 | # The packages folder can be ignored because of Package Restore 236 | **/[Pp]ackages/* 237 | # except build/, which is used as an MSBuild target. 238 | !**/[Pp]ackages/build/ 239 | # Uncomment if necessary however generally it will be regenerated when needed 240 | #!**/[Pp]ackages/repositories.config 241 | # NuGet v3's project.json files produces more ignorable files 242 | *.nuget.props 243 | *.nuget.targets 244 | 245 | # Microsoft Azure Build Output 246 | csx/ 247 | *.build.csdef 248 | 249 | # Microsoft Azure Emulator 250 | ecf/ 251 | rcf/ 252 | 253 | # Windows Store app package directories and files 254 | AppPackages/ 255 | BundleArtifacts/ 256 | Package.StoreAssociation.xml 257 | _pkginfo.txt 258 | *.appx 259 | *.appxbundle 260 | *.appxupload 261 | 262 | # Visual Studio cache files 263 | # files ending in .cache can be ignored 264 | *.[Cc]ache 265 | # but keep track of directories ending in .cache 266 | !?*.[Cc]ache/ 267 | 268 | # Others 269 | ClientBin/ 270 | ~$* 271 | *~ 272 | *.dbmdl 273 | *.dbproj.schemaview 274 | *.jfm 275 | *.pfx 276 | *.publishsettings 277 | orleans.codegen.cs 278 | 279 | # Including strong name files can present a security risk 280 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 281 | #*.snk 282 | 283 | # Since there are multiple workflows, uncomment next line to ignore bower_components 284 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 285 | #bower_components/ 286 | 287 | # RIA/Silverlight projects 288 | Generated_Code/ 289 | 290 | # Backup & report files from converting an old project file 291 | # to a newer Visual Studio version. Backup files are not needed, 292 | # because we have git ;-) 293 | _UpgradeReport_Files/ 294 | Backup*/ 295 | UpgradeLog*.XML 296 | UpgradeLog*.htm 297 | ServiceFabricBackup/ 298 | *.rptproj.bak 299 | 300 | # SQL Server files 301 | *.mdf 302 | *.ldf 303 | *.ndf 304 | 305 | # Business Intelligence projects 306 | *.rdl.data 307 | *.bim.layout 308 | *.bim_*.settings 309 | *.rptproj.rsuser 310 | *- [Bb]ackup.rdl 311 | *- [Bb]ackup ([0-9]).rdl 312 | *- [Bb]ackup ([0-9][0-9]).rdl 313 | 314 | # Microsoft Fakes 315 | FakesAssemblies/ 316 | 317 | # GhostDoc plugin setting file 318 | *.GhostDoc.xml 319 | 320 | # Node.js Tools for Visual Studio 321 | .ntvs_analysis.dat 322 | node_modules/ 323 | 324 | # Visual Studio 6 build log 325 | *.plg 326 | 327 | # Visual Studio 6 workspace options file 328 | *.opt 329 | 330 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 331 | *.vbw 332 | 333 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 334 | *.vbp 335 | 336 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 337 | *.dsw 338 | *.dsp 339 | 340 | # Visual Studio 6 technical files 341 | 342 | # Visual Studio LightSwitch build output 343 | **/*.HTMLClient/GeneratedArtifacts 344 | **/*.DesktopClient/GeneratedArtifacts 345 | **/*.DesktopClient/ModelManifest.xml 346 | **/*.Server/GeneratedArtifacts 347 | **/*.Server/ModelManifest.xml 348 | _Pvt_Extensions 349 | 350 | # Paket dependency manager 351 | .paket/paket.exe 352 | paket-files/ 353 | 354 | # FAKE - F# Make 355 | .fake/ 356 | 357 | # CodeRush personal settings 358 | .cr/personal 359 | 360 | # Python Tools for Visual Studio (PTVS) 361 | __pycache__/ 362 | *.pyc 363 | 364 | # Cake - Uncomment if you are using it 365 | # tools/** 366 | # !tools/packages.config 367 | 368 | # Tabs Studio 369 | *.tss 370 | 371 | # Telerik's JustMock configuration file 372 | *.jmconfig 373 | 374 | # BizTalk build output 375 | *.btp.cs 376 | *.btm.cs 377 | *.odx.cs 378 | *.xsd.cs 379 | 380 | # OpenCover UI analysis results 381 | OpenCover/ 382 | 383 | # Azure Stream Analytics local run output 384 | ASALocalRun/ 385 | 386 | # MSBuild Binary and Structured Log 387 | *.binlog 388 | 389 | # NVidia Nsight GPU debugger configuration file 390 | *.nvuser 391 | 392 | # MFractors (Xamarin productivity tool) working folder 393 | .mfractor/ 394 | 395 | # Local History for Visual Studio 396 | .localhistory/ 397 | 398 | # Visual Studio History (VSHistory) files 399 | .vshistory/ 400 | 401 | # BeatPulse healthcheck temp database 402 | healthchecksdb 403 | 404 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 405 | MigrationBackup/ 406 | 407 | # Ionide (cross platform F# VS Code tools) working folder 408 | .ionide/ 409 | 410 | # Fody - auto-generated XML schema 411 | FodyWeavers.xsd 412 | 413 | # VS Code files for those working on multiple tools 414 | *.code-workspace 415 | 416 | # Local History for Visual Studio Code 417 | 418 | # Windows Installer files from build outputs 419 | *.cab 420 | *.msi 421 | *.msix 422 | *.msm 423 | *.msp 424 | 425 | # JetBrains Rider 426 | *.sln.iml 427 | 428 | ### VisualStudio Patch ### 429 | # Additional files built by Visual Studio 430 | 431 | # End of https://www.toptal.com/developers/gitignore/api/powershell,visualstudiocode,visualstudio 432 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | <p align="center"> 2 | <a href="https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/actions/workflows/build-and-test-powershell-module.yml"><img alt="Build status" src="https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/actions/workflows/build-and-test-powershell-module.yml/badge.svg"></a> 3 | <a href="https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/actions/workflows/build-test-and-deploy-powershell-module.yml"><img alt="Deploy status" src="https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/actions/workflows/build-test-and-deploy-powershell-module.yml/badge.svg"></a> 4 | <a href="https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/blob/main/License.md"><img alt="License" src="https://img.shields.io/github/license/deadlydog/PowerShell.ScriptModuleRepositoryTemplate.svg"></a> 5 | <a href="https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate/blob/main/docs/Contributing.md"><img alt="PRs welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"></a> 6 | </p> 7 | 8 | <p align="center"> 9 | <a href="https://www.powershellgallery.com/packages/ScriptModuleRepositoryTemplate"><img alt="Stable PowerShell module version" src="https://img.shields.io/powershellgallery/v/ScriptModuleRepositoryTemplate.svg"></a> 10 | <a href="https://www.powershellgallery.com/packages/ScriptModuleRepositoryTemplate"><img alt="Prerelease PowerShell module version" src="https://img.shields.io/powershellgallery/vpre/ScriptModuleRepositoryTemplate.svg?include_prereleases&label=powershell%20gallery%20prerelease&colorB=yellow"></a> 11 | <a href="https://www.powershellgallery.com/packages/ScriptModuleRepositoryTemplate"><img src="https://img.shields.io/powershellgallery/dt/ScriptModuleRepositoryTemplate.svg"></a> 12 | </p> 13 | 14 | <p align="center"> 15 | <!-- Must add 'Windows', 'MacOS', and 'Linux' to the module manifest tags for them to show up on the Platforms badge. --> 16 | <img src="https://img.shields.io/powershellgallery/p/ScriptModuleRepositoryTemplate.svg"> 17 | <img src="https://img.shields.io/github/languages/top/deadlydog/PowerShell.ScriptModuleRepositoryTemplate.svg"> 18 | <img src="https://img.shields.io/github/languages/code-size/deadlydog/PowerShell.ScriptModuleRepositoryTemplate.svg"> 19 | </p> 20 | 21 | # PowerShell Script Module Repository Template 22 | 23 | A template repository and module for creating new PowerShell script module repos quickly with boilerplate files and CI/CD workflows already defined. 24 | 25 | ## ✨ Features 26 | 27 | Use this repo template or module for your new git repository to get the following features out-of-the-box: 28 | 29 | - GitHub Actions workflows (Azure DevOps Pipelines YAML support coming soon) that: 30 | - Publish a prerelease version on every commit to the `main` branch, and a stable version once manually approved. 31 | - Can also manually trigger deployments of feature branches. 32 | - Version the module. 33 | - Run PSScriptAnalyzer to ensure best practices are followed. 34 | - Run build tests with Pwsh and PowerShell to ensure backward compatibility. 35 | - Run smoke tests on multiple platforms (Windows, Linux, MacOS) to ensure the module works as expected after being installed on all platforms. 36 | - Publish the module to the PowerShell Gallery (custom feed support coming soon). 37 | - Spell check all files in the repository. 38 | - Display test code coverage results on PRs. 39 | - Visual Studio Code tasks to easily run Pester tests and PSScriptAnalyzer locally. 40 | - A `.devcontainer` for use with Visual Studio Code's [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) and [GitHub Codespaces](https://github.com/features/codespaces). 41 | - Boilerplate repository files, such as ReadMe, License, Changelog, .gitignore, .editorconfig, PR and Issue templates, and more. 42 | 43 | This template/module does not require any additional modules or dependencies to be installed on your machine, or force you to learn a new build framework or language. 44 | 45 | I plan on creating a dedicated video tutorial for this module and template, but until then you can [watch the demo portion of this presentation](https://www.youtube.com/watch?v=oM_2sOE9Y6g&t=1420s) that shows setting it up and using it, and talks about some of the benefits it provides. 46 | 47 | ## 🚀 Get started 48 | 49 | There are two ways to create your new PowerShell module repository: 50 | 51 | 1. Use the `New-PowerShellScriptModuleRepository` cmdlet to create a new repository, or 52 | 1. Create a new repository from this template in GitHub. 53 | 54 | Both of these methods are described in more detail below. 55 | 56 | Once the repository is created, follow the instructions in the repo's ReadMe file to complete the setup. 57 | The non-transformed instructions can also be [viewed here](/src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/ReadMe.md). 58 | 59 | ### 📂 Method 1: Use the New-PowerShellScriptModuleRepository cmdlet 60 | 61 | Step 1: Install the `ScriptModuleRepositoryTemplate` module [from the PowerShell Gallery](https://www.powershellgallery.com/packages/ScriptModuleRepositoryTemplate): 62 | 63 | ```powershell 64 | Install-Module -Name ScriptModuleRepositoryTemplate -Scope CurrentUser 65 | ``` 66 | 67 | Step 2: Create the new repository files: 68 | 69 | ```powershell 70 | New-PowerShellScriptModuleRepository -RepositoryDirectoryPath 'C:\MyRepoName' -ModuleName 'MyModuleName' -OrganizationName 'My Name' 71 | ``` 72 | 73 | The above command will create a new directory at `C:\MyRepoName` with the boilerplate files and workflows for publishing your module already set up. 74 | 75 | You can then perform a `git init` in that directory and push it to where you want your git repository hosted (e.g. Azure DevOps or GitHub). 76 | 77 | To complete the setup, follow the instructions in the module repo's ReadMe file. 78 | 79 | ### 📄 Method 2: Create repository from GitHub template 80 | 81 | If your repository will be hosted on GitHub, you can follow the steps below: 82 | 83 | #### 🗍 Step 1: Create a new repo from this template 84 | 85 | The official docs for creating a new repository from a template can [be found here](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template). 86 | In short, the steps are: 87 | 88 | 1. Click the `Use this template` button at the top of the repository and choose `Create a new repository`. 89 | 1. Name your new repository (including your module's name is a good idea) and give it a description. 90 | 1. Click the `Create repository` button. 91 | 1. You should now have the new repository in your account with the name you chose. 92 | 1. Clone your new repository to your local machine to start making changes to it. 93 | 94 | #### 🤖 Step 2: Replace repo template information 95 | 96 | Run the [_InitializeRepository.ps1](/_InitializeRepository.ps1) script to update the repository files with your module's information. 97 | You will be prompted to enter some information, such as: 98 | 99 | - Your module's name (no spaces) 100 | - Your name or organization name (may contain spaces) 101 | 102 | Once the script completes, most of the repo files will be replaced. 103 | You should commit the changes. 104 | 105 | To complete the setup, follow the instructions in the repo's new ReadMe file (that replaced this one). 106 | 107 | ## 📋 Create your own template (optional) 108 | 109 | Not happy with some of the default template configurations? 110 | Maybe you don't like the .editorconfig settings, or want it to publish to your own internal PowerShell Gallery feed by default? 111 | You can derive your own template from this repository and use it for your future modules, minimizing the custom changes you need to make every time you create a new repo. 112 | 113 | To create your own template: 114 | 115 | 1. Fork [this repository on GitHub](https://github.com/deadlydog/PowerShell.ScriptModuleRepositoryTemplate). 116 | 1. In GitHub, from your repo's `Settings` tab under the `General` section, rename the repository to reflect that it is a template and check the box to make it a `Template repository`. 117 | 1. Modify [the template repo files](/src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/) with whatever customizations you want. 118 | 1. If you are introducing more replacement tokens in the files, you will need to update the `Set-TemplateTokenValuesInAllRepositoryFiles` function in the [ScriptModuleRepositoryTemplate.psm1](/src/ScriptModuleRepositoryTemplate/ScriptModuleRepositoryTemplate.psm1) file to handle them. 119 | 120 | You can now create new repositories from your GitHub template in the same way you would use this one. 121 | 122 | If you want to be able to create new repositories from a module, you will need to publish your module under a different name. 123 | 124 | ## ⏪ Changelog 125 | 126 | See what's changed in the module and template over time by viewing [the changelog](Changelog.md). 127 | 128 | ## ❤ Donate to support this module and template 129 | 130 | Buy me a hot apple cider for providing this module and template open source and for free 🙂 131 | 132 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5MWSTSXNYEJWW) 133 | 134 | ## TODO 135 | 136 | Things to still do: 137 | 138 | - Add support for Azure DevOps. 139 | - Allow user to create single .psm1 file or Public/Private directory structure for separate files per function. 140 | - Allow using a custom PowerShell Gallery feed URL. 141 | - Have screenshots and/or recordings of manual steps to setup GitHub and Azure DevOps, since they involve clicking around in the UI. 142 | Perhaps link to [this tutorial for Azure DevOps](https://dev.to/olalekan_oladiran_d74b7a6/how-to-enable-continuous-integration-with-azure-pipelines-1doi)? 143 | - Perhaps we can automate this to avoid the manual steps altogether? 144 | - Maybe support new deployments on tag creation. 145 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.github/workflows/build-and-test-powershell-module.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: main 6 | 7 | # Allows you to run this workflow manually from the Actions tab. 8 | workflow_dispatch: 9 | 10 | # Allows the deployment workflow to call this workflow. 11 | workflow_call: 12 | inputs: 13 | versionNumber: 14 | description: 'The version number to use for the module. This should be in the format of "Major.Minor.Patch". e.g. "1.0.0". Future builds will increment from this version number. This input is optional. If not provided, the previous version numbers Patch will be incremented.' 15 | required: false 16 | type: string 17 | default: '' 18 | # Outputs required by the deployment workflow. 19 | outputs: 20 | powerShellModuleName: 21 | description: 'The name of the PowerShell module being built.' 22 | value: ${{ jobs.build-and-test.outputs.powerShellModuleName }} 23 | stableVersionNumber: 24 | description: 'The stable version number of the PowerShell module created by the build.' 25 | value: ${{ jobs.build-and-test.outputs.stableVersionNumber }} 26 | prereleaseVersionNumber: 27 | description: 'The full prerelease version number of the PowerShell module created by the build.' 28 | value: ${{ jobs.build-and-test.outputs.prereleaseVersionNumber }} 29 | prereleaseVersionLabel: 30 | description: 'The prerelease label of the PowerShell module created by the build.' 31 | value: ${{ jobs.build-and-test.outputs.prereleaseVersionLabel }} 32 | moduleArtifactName: 33 | description: 'The name of the module artifact created by the build.' 34 | value: ${{ jobs.build-and-test.outputs.moduleArtifactName }} 35 | deployFilesArtifactName: 36 | description: 'The name of the deploy files artifact created by the build.' 37 | value: ${{ jobs.build-and-test.outputs.deployFilesArtifactName }} 38 | 39 | env: 40 | powerShellModuleName: '__NewModuleName__' 41 | powerShellModuleDirectoryPath: './src/__NewModuleName__' 42 | deployFilesDirectoryPath: './deploy' 43 | moduleArtifactName: 'ModuleArtifact' 44 | moduleArtifactDirectoryPath: './artifacts/Module' 45 | deployFilesArtifactName: 'DeployFilesArtifact' 46 | deployFilesArtifactDirectoryPath: './artifacts/deploy' 47 | 48 | jobs: 49 | build-and-test: 50 | runs-on: windows-latest # Use Windows agent so we can run Pester tests on Windows PowerShell 5.1 as well. 51 | outputs: 52 | powerShellModuleName: ${{ env.powerShellModuleName }} 53 | stableVersionNumber: ${{ steps.version-number.outputs.majorMinorPatch }} 54 | prereleaseVersionNumber: ${{ steps.version-number.outputs.majorMinorPatch }}-${{ steps.version-number.outputs.prereleaseLabel }} 55 | prereleaseVersionLabel: ${{ steps.version-number.outputs.prereleaseLabel}} 56 | moduleArtifactName: ${{ env.moduleArtifactName }} 57 | deployFilesArtifactName: ${{ env.deployFilesArtifactName }} 58 | steps: 59 | - name: Checkout the repo source code 60 | uses: actions/checkout@v4 61 | with: 62 | fetch-depth: 0 # Fetch all history so that GitVersion can determine the version number. 63 | 64 | - name: Display PowerShell version and OS details in case needed for troubleshooting 65 | shell: pwsh 66 | run: $PSVersionTable 67 | 68 | # If you do not want to use spellcheck, delete this step and the .cspell.json file in the repository root. 69 | - name: Run spellcheck 70 | uses: streetsidesoftware/cspell-action@v5 71 | 72 | - name: Install GitVersion 73 | uses: gittools/actions/gitversion/setup@v0 74 | with: 75 | versionSpec: '5.x' 76 | 77 | - name: Get git metadata used to determine new version number 78 | id: git-version 79 | uses: gittools/actions/gitversion/execute@v0 80 | 81 | - name: Determine the new version number 82 | id: version-number 83 | shell: pwsh 84 | run: | 85 | [string] $newVersionNumber = '${{ steps.git-version.outputs.majorMinorPatch }}' 86 | [string] $prereleaseLabel = '${{ steps.git-version.outputs.preReleaseTag }}' 87 | 88 | [string] $manuallyProvidedVersionNumber = '${{ inputs.versionNumber }}' 89 | if (-not [string]::IsNullOrWhiteSpace($manuallyProvidedVersionNumber)) { 90 | Write-Output "Using manually provided version number '$manuallyProvidedVersionNumber'." 91 | $newVersionNumber = $manuallyProvidedVersionNumber 92 | } 93 | 94 | # The preReleaseTag is empty when building the default branch, so manually create a prerelease version number if needed. 95 | if ([string]::IsNullOrWhiteSpace($prereleaseLabel)) { 96 | [string] $dateTime = (Get-Date -Format 'yyyyMMddTHHmmss') 97 | $prereleaseLabel = 'CI' + $dateTime + 'SHA' + '${{ steps.git-version.outputs.shortSha }}' 98 | } 99 | # PowerShell prerelease labels can only contain the characters 'a-zA-Z0-9', so sanitize it if needed. 100 | $newVersionNumberPrereleaseLabel = $prereleaseLabel -replace '[^a-zA-Z0-9]', '' 101 | 102 | Write-Output "Setting step output variables 'majorMinorPatch=$newVersionNumber' and 'prereleaseLabel=$newVersionNumberPrereleaseLabel'." 103 | "majorMinorPatch=$newVersionNumber" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append 104 | "prereleaseLabel=$newVersionNumberPrereleaseLabel" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append 105 | 106 | # Suppress rules if needed: https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer 107 | - name: Run PowerShell linter with PSScriptAnalyzer 108 | shell: pwsh 109 | run: Invoke-ScriptAnalyzer -Path . -Recurse -EnableExit 110 | 111 | - name: Run Pester tests on Windows PowerShell to ensure backward compatibility 112 | shell: powershell 113 | run: | 114 | Write-Output "Pester version being used:" 115 | Import-Module -Name Pester 116 | Get-Module -Name Pester 117 | 118 | Write-Output "Running all Pester tests in the repo:" 119 | $pesterConfig = New-PesterConfiguration @{ 120 | Output = @{ Verbosity = 'Detailed' } 121 | Run = @{ Throw = $true } 122 | TestResult = @{ 123 | Enabled = $true 124 | OutputPath = 'test-results-windows-powershell-nunit.xml' 125 | } 126 | CodeCoverage = @{ Enabled = $false } 127 | } 128 | Invoke-Pester -Configuration $pesterConfig 129 | 130 | - name: Run Pester tests and generate code coverage report 131 | shell: pwsh 132 | run: | 133 | Write-Output "Pester version being used:" 134 | Import-Module -Name Pester 135 | Get-Module -Name Pester 136 | 137 | Write-Output "Running all Pester tests in the repo:" 138 | $pesterConfig = New-PesterConfiguration @{ 139 | Output = @{ Verbosity = 'Detailed' } 140 | Run = @{ Throw = $true } 141 | TestResult = @{ 142 | Enabled = $true 143 | OutputPath = 'test-results-pwsh-nunit.xml' 144 | } 145 | CodeCoverage = @{ 146 | Enabled = $true 147 | OutputPath = 'code-coverage-jacoco.xml' 148 | Path = 'src/' # Only include code coverage for the module's source code, not build or deployment scripts. 149 | } 150 | } 151 | Invoke-Pester -Configuration $pesterConfig 152 | 153 | - name: Add code coverage report to PR 154 | # Adding the code coverage report is not supported for manual workflow runs. 155 | if: github.event_name != 'workflow_dispatch' 156 | uses: madrapps/jacoco-report@v1.6.1 157 | with: 158 | paths: code-coverage-jacoco.xml 159 | token: ${{ secrets.GITHUB_TOKEN }} 160 | # If you want to fail the build if the coverage is below a certain threshold, you can use the following options. 161 | # min-coverage-overall: 60 162 | # min-coverage-changed-files: 60 163 | 164 | - name: Create the module artifact 165 | shell: pwsh 166 | run: | 167 | Write-Output "Reading in environment variables." 168 | [string] $moduleName = $Env:powerShellModuleName 169 | [string] $moduleDirectoryPath = $Env:powerShellModuleDirectoryPath 170 | [string] $moduleManifestFileName = $moduleName + '.psd1' 171 | [string] $moduleManifestFilePath = Join-Path -Path $moduleDirectoryPath -ChildPath $moduleManifestFileName 172 | [string] $moduleArtifactDirectoryPath = Join-Path -Path $Env:moduleArtifactDirectoryPath -ChildPath $moduleName 173 | [string] $newVersionNumber = '${{ steps.version-number.outputs.majorMinorPatch}}' 174 | 175 | Write-Output "Updating the version number of the module manifest file '$moduleManifestFilePath' to '$newVersionNumber'." 176 | Update-ModuleManifest -Path $moduleManifestFilePath -ModuleVersion $newVersionNumber 177 | 178 | Write-Output "Testing the module manifest file '$moduleManifestFilePath' to ensure it is valid." 179 | Test-ModuleManifest -Path $moduleManifestFilePath 180 | 181 | Write-Output "Copying the module files to the module artifact directory '$moduleArtifactDirectoryPath'." 182 | Copy-Item -Path $moduleDirectoryPath -Destination $moduleArtifactDirectoryPath -Exclude '*.Tests.ps1' -Recurse -Force 183 | 184 | - name: Create deploy files artifact 185 | shell: pwsh 186 | run: | 187 | [string] $deployFilesDirectoryPath = $Env:deployFilesDirectoryPath 188 | [string] $deployFilesArtifactDirectoryPath = $Env:deployFilesArtifactDirectoryPath 189 | 190 | Write-Output "Copying the deployment files '$deployFilesDirectoryPath' to the deployment artifact directory '$deployFilesArtifactDirectoryPath'." 191 | Copy-Item -Path $deployFilesDirectoryPath -Destination $deployFilesArtifactDirectoryPath -Recurse -Force 192 | 193 | - name: Set the new version tag 194 | # Only run this step if we are doing a push (not a PR) to the default branch (e.g. main). 195 | if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) 196 | shell: pwsh 197 | run: | 198 | [string] $newVersionNumber = '${{ steps.version-number.outputs.majorMinorPatch}}' 199 | [string] $newVersionTag = "v$newVersionNumber" 200 | 201 | # To avoid a 403 error on 'git push', ensure you have granted your GitHub Actions workflow read/write permission. 202 | # In your GitHub repo: Settings > Actions > General > Workflow permissions > Read and write permissions 203 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#configuring-the-default-github_token-permissions 204 | 205 | Write-Output "Tagging commit with new version tag '$newVersionTag'." 206 | & git tag $newVersionTag 207 | & git push origin $newVersionTag 208 | 209 | - name: Upload module artifact 210 | uses: actions/upload-artifact@v4 211 | with: 212 | name: ${{ env.moduleArtifactName }} 213 | path: ${{ env.moduleArtifactDirectoryPath }} 214 | 215 | - name: Upload deploy files artifact 216 | uses: actions/upload-artifact@v4 217 | with: 218 | name: ${{ env.deployFilesArtifactName }} 219 | path: ${{ env.deployFilesArtifactDirectoryPath }} 220 | -------------------------------------------------------------------------------- /.github/workflows/build-test-and-deploy-powershell-module.yml: -------------------------------------------------------------------------------- 1 | name: build-and-deploy 2 | 3 | on: 4 | push: 5 | branches: main 6 | paths: [ "src/**", "build/**", "deploy/**", ".github/workflows/**", ".cspell.json" ] 7 | 8 | # Allows you to run this workflow manually from the Actions tab. 9 | workflow_dispatch: 10 | inputs: 11 | versionNumber: 12 | description: 'The version number to use for the module. This should be in the format of "Major.Minor.Patch". e.g. "1.0.0". Future builds will increment from this version number. This input is optional. If not provided, the previous version numbers Patch will be incremented.' 13 | required: false 14 | type: string 15 | default: '' 16 | 17 | env: 18 | artifactsDirectoryPath: './artifacts' 19 | 20 | jobs: 21 | run-build-and-test: 22 | uses: ./.github/workflows/build-and-test-powershell-module.yml 23 | with: 24 | versionNumber: ${{ github.event.inputs.versionNumber }} 25 | 26 | publish-prerelease-module: 27 | needs: run-build-and-test 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Download module artifact 31 | uses: actions/download-artifact@v4 32 | with: 33 | name: ${{ needs.run-build-and-test.outputs.moduleArtifactName }} 34 | path: ${{ env.artifactsDirectoryPath }} 35 | 36 | - name: Publish prerelease PowerShell module 37 | shell: pwsh 38 | run: | 39 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 40 | [string] $moduleDirectoryPath = "$Env:artifactsDirectoryPath/$moduleName" 41 | [string] $moduleManifestFilePath = Join-Path -Path $moduleDirectoryPath -ChildPath "$moduleName.psd1" 42 | [string] $prereleaseVersionLabel = '${{ needs.run-build-and-test.outputs.prereleaseVersionLabel}}' 43 | 44 | Write-Output "Updating the module manifest version number's prerelease label to '$prereleaseVersionLabel'." 45 | Update-ModuleManifest -Path $moduleManifestFilePath -Prerelease $prereleaseVersionLabel 46 | 47 | Write-Output "Testing the prerelease module manifest file '$moduleManifestFilePath' to ensure it is still valid." 48 | Test-ModuleManifest -Path $moduleManifestFilePath 49 | 50 | Write-Output "Publishing the prerelease version of the module." 51 | Publish-Module -Path $moduleDirectoryPath -NuGetApiKey '${{ secrets.POWERSHELL_GALLERY_API_KEY }}' -Verbose 52 | 53 | - name: Wait a short while for the module to be available on the PowerShell Gallery before continuing 54 | shell: pwsh 55 | run: Start-Sleep -Seconds 30 56 | 57 | test-prerelease-module-in-pwsh: 58 | needs: [run-build-and-test, publish-prerelease-module] 59 | strategy: 60 | matrix: 61 | os: [ubuntu-latest, windows-latest, macOS-latest] 62 | runs-on: ${{ matrix.os }} 63 | steps: 64 | - name: Display PowerShell version being used 65 | shell: pwsh 66 | run: $PSVersionTable 67 | 68 | - name: Install prerelease module from PowerShell Gallery 69 | shell: pwsh 70 | run: | 71 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 72 | [string] $prereleaseVersionNumber = '${{ needs.run-build-and-test.outputs.prereleaseVersionNumber}}' 73 | 74 | Write-Output "Installing the module '$moduleName' prerelease version '$prereleaseVersionNumber' from the PowerShell Gallery." 75 | Install-Module -Name $moduleName -AllowPrerelease -RequiredVersion $prereleaseVersionNumber -Force -Scope CurrentUser -Repository PSGallery -ErrorAction Stop -Verbose 76 | 77 | - name: Download deploy files artifact 78 | uses: actions/download-artifact@v4 79 | with: 80 | name: ${{ needs.run-build-and-test.outputs.deployFilesArtifactName}} 81 | path: ${{ env.artifactsDirectoryPath }} 82 | 83 | - name: Run smoke tests 84 | shell: pwsh 85 | run: | 86 | [string] $smokeTestsScriptPath = "$Env:artifactsDirectoryPath/Invoke-SmokeTests.ps1" 87 | 88 | Write-Output "Running Pester smoke tests from file '$smokeTestsScriptPath'." 89 | $pesterConfig = New-PesterConfiguration @{ 90 | Output = @{ Verbosity = 'Detailed' } 91 | Run = @{ 92 | Path = $smokeTestsScriptPath 93 | Throw = $true 94 | } 95 | } 96 | Invoke-Pester -Configuration $pesterConfig 97 | 98 | Write-Output "Displaying the installed module version that was used for the smoke tests." 99 | Get-Module -Name '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 100 | 101 | test-prerelease-module-in-windows-powershell: 102 | needs: [run-build-and-test, publish-prerelease-module] 103 | runs-on: windows-latest 104 | steps: 105 | - name: Display PowerShell version being used 106 | shell: powershell 107 | run: $PSVersionTable 108 | 109 | - name: Install prerelease module from PowerShell Gallery 110 | shell: powershell 111 | run: | 112 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 113 | [string] $prereleaseVersionNumber = '${{ needs.run-build-and-test.outputs.prereleaseVersionNumber}}' 114 | 115 | Write-Output "Installing the module '$moduleName' prerelease version '$prereleaseVersionNumber' from the PowerShell Gallery." 116 | Install-Module -Name $moduleName -AllowPrerelease -RequiredVersion $prereleaseVersionNumber -Force -Scope CurrentUser -Repository PSGallery -ErrorAction Stop -Verbose 117 | 118 | - name: Download deploy files artifact from triggered workflow 119 | uses: actions/download-artifact@v4 120 | with: 121 | name: ${{ needs.run-build-and-test.outputs.deployFilesArtifactName}} 122 | path: ${{ env.artifactsDirectoryPath }} 123 | 124 | - name: Run smoke tests 125 | shell: powershell 126 | run: | 127 | [string] $smokeTestsScriptPath = "$Env:artifactsDirectoryPath/Invoke-SmokeTests.ps1" 128 | 129 | Write-Output "Running Pester smoke tests from file '$smokeTestsScriptPath'." 130 | $pesterConfig = New-PesterConfiguration @{ 131 | Output = @{ Verbosity = 'Detailed' } 132 | Run = @{ 133 | Path = $smokeTestsScriptPath 134 | Throw = $true 135 | } 136 | } 137 | Invoke-Pester -Configuration $pesterConfig 138 | 139 | Write-Output "Displaying the installed module version that was used for the smoke tests." 140 | Get-Module -Name '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 141 | 142 | publish-stable-module: 143 | needs: [run-build-and-test, test-prerelease-module-in-pwsh, test-prerelease-module-in-windows-powershell] 144 | runs-on: ubuntu-latest 145 | environment: production # Used for deployment approvals. 146 | steps: 147 | - name: Download module artifact from triggered workflow 148 | uses: actions/download-artifact@v4 149 | with: 150 | name: ${{ needs.run-build-and-test.outputs.moduleArtifactName}} 151 | path: ${{ env.artifactsDirectoryPath }} 152 | 153 | - name: Publish stable PowerShell module 154 | shell: pwsh 155 | run: | 156 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 157 | [string] $moduleDirectoryPath = "$Env:artifactsDirectoryPath/$moduleName" 158 | Publish-Module -Path $moduleDirectoryPath -NuGetApiKey '${{ secrets.POWERSHELL_GALLERY_API_KEY }}' -Verbose 159 | 160 | - name: Wait a short while for the module to be available on the PowerShell Gallery before continuing 161 | shell: pwsh 162 | run: Start-Sleep -Seconds 30 163 | 164 | test-stable-module-in-pwsh: 165 | needs: [run-build-and-test, publish-stable-module] 166 | strategy: 167 | matrix: 168 | os: [ubuntu-latest, windows-latest, macOS-latest] 169 | runs-on: ${{ matrix.os }} 170 | steps: 171 | - name: Display PowerShell version being used 172 | shell: pwsh 173 | run: $PSVersionTable 174 | 175 | - name: Install stable module from PowerShell Gallery 176 | shell: pwsh 177 | run: | 178 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 179 | [string] $stableVersionNumber = '${{ needs.run-build-and-test.outputs.stableVersionNumber}}' 180 | 181 | Write-Output "Installing the module '$moduleName' stable version '$stableVersionNumber' from the PowerShell Gallery." 182 | Install-Module -Name $moduleName -RequiredVersion $stableVersionNumber -Force -Scope CurrentUser -Repository PSGallery -ErrorAction Stop -Verbose 183 | 184 | - name: Download deploy files artifact from triggered workflow 185 | uses: actions/download-artifact@v4 186 | with: 187 | name: ${{ needs.run-build-and-test.outputs.deployFilesArtifactName}} 188 | path: ${{ env.artifactsDirectoryPath }} 189 | 190 | - name: Run smoke tests 191 | shell: pwsh 192 | run: | 193 | [string] $smokeTestsScriptPath = "$Env:artifactsDirectoryPath/Invoke-SmokeTests.ps1" 194 | 195 | Write-Output "Running Pester smoke tests from file '$smokeTestsScriptPath'." 196 | $pesterConfig = New-PesterConfiguration @{ 197 | Output = @{ Verbosity = 'Detailed' } 198 | Run = @{ 199 | Path = $smokeTestsScriptPath 200 | Throw = $true 201 | } 202 | } 203 | Invoke-Pester -Configuration $pesterConfig 204 | 205 | Write-Output "Displaying the installed module version that was used for the smoke tests." 206 | Get-Module -Name '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 207 | 208 | test-stable-module-in-windows-powershell: 209 | needs: [run-build-and-test, publish-stable-module] 210 | runs-on: windows-latest 211 | steps: 212 | - name: Display PowerShell version being used 213 | shell: powershell 214 | run: $PSVersionTable 215 | 216 | - name: Install stable module from PowerShell Gallery 217 | shell: powershell 218 | run: | 219 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 220 | [string] $stableVersionNumber = '${{ needs.run-build-and-test.outputs.stableVersionNumber}}' 221 | 222 | Write-Output "Installing the module '$moduleName' stable version '$stableVersionNumber' from the PowerShell Gallery." 223 | Install-Module -Name $moduleName -RequiredVersion $stableVersionNumber -Force -Scope CurrentUser -Repository PSGallery -ErrorAction Stop -Verbose 224 | 225 | - name: Download deploy files artifact from triggered workflow 226 | uses: actions/download-artifact@v4 227 | with: 228 | name: ${{ needs.run-build-and-test.outputs.deployFilesArtifactName}} 229 | path: ${{ env.artifactsDirectoryPath }} 230 | 231 | - name: Run smoke tests 232 | shell: powershell 233 | run: | 234 | [string] $smokeTestsScriptPath = "$Env:artifactsDirectoryPath/Invoke-SmokeTests.ps1" 235 | 236 | Write-Output "Running Pester smoke tests from file '$smokeTestsScriptPath'." 237 | $pesterConfig = New-PesterConfiguration @{ 238 | Output = @{ Verbosity = 'Detailed' } 239 | Run = @{ 240 | Path = $smokeTestsScriptPath 241 | Throw = $true 242 | } 243 | } 244 | Invoke-Pester -Configuration $pesterConfig 245 | 246 | Write-Output "Displaying the installed module version that was used for the smoke tests." 247 | Get-Module -Name '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 248 | -------------------------------------------------------------------------------- /src/ScriptModuleRepositoryTemplate/TemplateRepoFiles/_.github/workflows/build-test-and-deploy-powershell-module.yml: -------------------------------------------------------------------------------- 1 | name: build-and-deploy 2 | 3 | on: 4 | push: 5 | branches: main 6 | paths: [ "src/**", "build/**", "deploy/**", ".github/workflows/**", ".cspell.json" ] 7 | 8 | # Allows you to run this workflow manually from the Actions tab. 9 | workflow_dispatch: 10 | inputs: 11 | versionNumber: 12 | description: 'The version number to use for the module. This should be in the format of "Major.Minor.Patch". e.g. "1.0.0". Future builds will increment from this version number. This input is optional. If not provided, the previous version numbers Patch will be incremented.' 13 | required: false 14 | type: string 15 | default: '' 16 | 17 | env: 18 | artifactsDirectoryPath: './artifacts' 19 | 20 | jobs: 21 | run-build-and-test: 22 | uses: ./.github/workflows/build-and-test-powershell-module.yml 23 | with: 24 | versionNumber: ${{ github.event.inputs.versionNumber }} 25 | 26 | publish-prerelease-module: 27 | needs: run-build-and-test 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Download module artifact 31 | uses: actions/download-artifact@v4 32 | with: 33 | name: ${{ needs.run-build-and-test.outputs.moduleArtifactName }} 34 | path: ${{ env.artifactsDirectoryPath }} 35 | 36 | - name: Publish prerelease PowerShell module 37 | shell: pwsh 38 | run: | 39 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 40 | [string] $moduleDirectoryPath = "$Env:artifactsDirectoryPath/$moduleName" 41 | [string] $moduleManifestFilePath = Join-Path -Path $moduleDirectoryPath -ChildPath "$moduleName.psd1" 42 | [string] $prereleaseVersionLabel = '${{ needs.run-build-and-test.outputs.prereleaseVersionLabel}}' 43 | 44 | Write-Output "Updating the module manifest version number's prerelease label to '$prereleaseVersionLabel'." 45 | Update-ModuleManifest -Path $moduleManifestFilePath -Prerelease $prereleaseVersionLabel 46 | 47 | Write-Output "Testing the prerelease module manifest file '$moduleManifestFilePath' to ensure it is still valid." 48 | Test-ModuleManifest -Path $moduleManifestFilePath 49 | 50 | Write-Output "Publishing the prerelease version of the module." 51 | Publish-Module -Path $moduleDirectoryPath -NuGetApiKey '${{ secrets.POWERSHELL_GALLERY_API_KEY }}' -Verbose 52 | 53 | - name: Wait a short while for the module to be available on the PowerShell Gallery before continuing 54 | shell: pwsh 55 | run: Start-Sleep -Seconds 30 56 | 57 | test-prerelease-module-in-pwsh: 58 | needs: [run-build-and-test, publish-prerelease-module] 59 | strategy: 60 | matrix: 61 | os: [ubuntu-latest, windows-latest, macOS-latest] 62 | runs-on: ${{ matrix.os }} 63 | steps: 64 | - name: Display PowerShell version being used 65 | shell: pwsh 66 | run: $PSVersionTable 67 | 68 | - name: Install prerelease module from PowerShell Gallery 69 | shell: pwsh 70 | run: | 71 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 72 | [string] $prereleaseVersionNumber = '${{ needs.run-build-and-test.outputs.prereleaseVersionNumber}}' 73 | 74 | Write-Output "Installing the module '$moduleName' prerelease version '$prereleaseVersionNumber' from the PowerShell Gallery." 75 | Install-Module -Name $moduleName -AllowPrerelease -RequiredVersion $prereleaseVersionNumber -Force -Scope CurrentUser -Repository PSGallery -ErrorAction Stop -Verbose 76 | 77 | - name: Download deploy files artifact 78 | uses: actions/download-artifact@v4 79 | with: 80 | name: ${{ needs.run-build-and-test.outputs.deployFilesArtifactName}} 81 | path: ${{ env.artifactsDirectoryPath }} 82 | 83 | - name: Run smoke tests 84 | shell: pwsh 85 | run: | 86 | [string] $smokeTestsScriptPath = "$Env:artifactsDirectoryPath/Invoke-SmokeTests.ps1" 87 | 88 | Write-Output "Running Pester smoke tests from file '$smokeTestsScriptPath'." 89 | $pesterConfig = New-PesterConfiguration @{ 90 | Output = @{ Verbosity = 'Detailed' } 91 | Run = @{ 92 | Path = $smokeTestsScriptPath 93 | Throw = $true 94 | } 95 | } 96 | Invoke-Pester -Configuration $pesterConfig 97 | 98 | Write-Output "Displaying the installed module version that was used for the smoke tests." 99 | Get-Module -Name '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 100 | 101 | test-prerelease-module-in-windows-powershell: 102 | needs: [run-build-and-test, publish-prerelease-module] 103 | runs-on: windows-latest 104 | steps: 105 | - name: Display PowerShell version being used 106 | shell: powershell 107 | run: $PSVersionTable 108 | 109 | - name: Install prerelease module from PowerShell Gallery 110 | shell: powershell 111 | run: | 112 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 113 | [string] $prereleaseVersionNumber = '${{ needs.run-build-and-test.outputs.prereleaseVersionNumber}}' 114 | 115 | Write-Output "Installing the module '$moduleName' prerelease version '$prereleaseVersionNumber' from the PowerShell Gallery." 116 | Install-Module -Name $moduleName -AllowPrerelease -RequiredVersion $prereleaseVersionNumber -Force -Scope CurrentUser -Repository PSGallery -ErrorAction Stop -Verbose 117 | 118 | - name: Download deploy files artifact from triggered workflow 119 | uses: actions/download-artifact@v4 120 | with: 121 | name: ${{ needs.run-build-and-test.outputs.deployFilesArtifactName}} 122 | path: ${{ env.artifactsDirectoryPath }} 123 | 124 | - name: Run smoke tests 125 | shell: powershell 126 | run: | 127 | [string] $smokeTestsScriptPath = "$Env:artifactsDirectoryPath/Invoke-SmokeTests.ps1" 128 | 129 | Write-Output "Running Pester smoke tests from file '$smokeTestsScriptPath'." 130 | $pesterConfig = New-PesterConfiguration @{ 131 | Output = @{ Verbosity = 'Detailed' } 132 | Run = @{ 133 | Path = $smokeTestsScriptPath 134 | Throw = $true 135 | } 136 | } 137 | Invoke-Pester -Configuration $pesterConfig 138 | 139 | Write-Output "Displaying the installed module version that was used for the smoke tests." 140 | Get-Module -Name '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 141 | 142 | publish-stable-module: 143 | needs: [run-build-and-test, test-prerelease-module-in-pwsh, test-prerelease-module-in-windows-powershell] 144 | runs-on: ubuntu-latest 145 | environment: production # Used for deployment approvals. 146 | steps: 147 | - name: Download module artifact from triggered workflow 148 | uses: actions/download-artifact@v4 149 | with: 150 | name: ${{ needs.run-build-and-test.outputs.moduleArtifactName}} 151 | path: ${{ env.artifactsDirectoryPath }} 152 | 153 | - name: Publish stable PowerShell module 154 | shell: pwsh 155 | run: | 156 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 157 | [string] $moduleDirectoryPath = "$Env:artifactsDirectoryPath/$moduleName" 158 | Publish-Module -Path $moduleDirectoryPath -NuGetApiKey '${{ secrets.POWERSHELL_GALLERY_API_KEY }}' -Verbose 159 | 160 | - name: Wait a short while for the module to be available on the PowerShell Gallery before continuing 161 | shell: pwsh 162 | run: Start-Sleep -Seconds 30 163 | 164 | test-stable-module-in-pwsh: 165 | needs: [run-build-and-test, publish-stable-module] 166 | strategy: 167 | matrix: 168 | os: [ubuntu-latest, windows-latest, macOS-latest] 169 | runs-on: ${{ matrix.os }} 170 | steps: 171 | - name: Display PowerShell version being used 172 | shell: pwsh 173 | run: $PSVersionTable 174 | 175 | - name: Install stable module from PowerShell Gallery 176 | shell: pwsh 177 | run: | 178 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 179 | [string] $stableVersionNumber = '${{ needs.run-build-and-test.outputs.stableVersionNumber}}' 180 | 181 | Write-Output "Installing the module '$moduleName' stable version '$stableVersionNumber' from the PowerShell Gallery." 182 | Install-Module -Name $moduleName -RequiredVersion $stableVersionNumber -Force -Scope CurrentUser -Repository PSGallery -ErrorAction Stop -Verbose 183 | 184 | - name: Download deploy files artifact from triggered workflow 185 | uses: actions/download-artifact@v4 186 | with: 187 | name: ${{ needs.run-build-and-test.outputs.deployFilesArtifactName}} 188 | path: ${{ env.artifactsDirectoryPath }} 189 | 190 | - name: Run smoke tests 191 | shell: pwsh 192 | run: | 193 | [string] $smokeTestsScriptPath = "$Env:artifactsDirectoryPath/Invoke-SmokeTests.ps1" 194 | 195 | Write-Output "Running Pester smoke tests from file '$smokeTestsScriptPath'." 196 | $pesterConfig = New-PesterConfiguration @{ 197 | Output = @{ Verbosity = 'Detailed' } 198 | Run = @{ 199 | Path = $smokeTestsScriptPath 200 | Throw = $true 201 | } 202 | } 203 | Invoke-Pester -Configuration $pesterConfig 204 | 205 | Write-Output "Displaying the installed module version that was used for the smoke tests." 206 | Get-Module -Name '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 207 | 208 | test-stable-module-in-windows-powershell: 209 | needs: [run-build-and-test, publish-stable-module] 210 | runs-on: windows-latest 211 | steps: 212 | - name: Display PowerShell version being used 213 | shell: powershell 214 | run: $PSVersionTable 215 | 216 | - name: Install stable module from PowerShell Gallery 217 | shell: powershell 218 | run: | 219 | [string] $moduleName = '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 220 | [string] $stableVersionNumber = '${{ needs.run-build-and-test.outputs.stableVersionNumber}}' 221 | 222 | Write-Output "Installing the module '$moduleName' stable version '$stableVersionNumber' from the PowerShell Gallery." 223 | Install-Module -Name $moduleName -RequiredVersion $stableVersionNumber -Force -Scope CurrentUser -Repository PSGallery -ErrorAction Stop -Verbose 224 | 225 | - name: Download deploy files artifact from triggered workflow 226 | uses: actions/download-artifact@v4 227 | with: 228 | name: ${{ needs.run-build-and-test.outputs.deployFilesArtifactName}} 229 | path: ${{ env.artifactsDirectoryPath }} 230 | 231 | - name: Run smoke tests 232 | shell: powershell 233 | run: | 234 | [string] $smokeTestsScriptPath = "$Env:artifactsDirectoryPath/Invoke-SmokeTests.ps1" 235 | 236 | Write-Output "Running Pester smoke tests from file '$smokeTestsScriptPath'." 237 | $pesterConfig = New-PesterConfiguration @{ 238 | Output = @{ Verbosity = 'Detailed' } 239 | Run = @{ 240 | Path = $smokeTestsScriptPath 241 | Throw = $true 242 | } 243 | } 244 | Invoke-Pester -Configuration $pesterConfig 245 | 246 | Write-Output "Displaying the installed module version that was used for the smoke tests." 247 | Get-Module -Name '${{ needs.run-build-and-test.outputs.powerShellModuleName }}' 248 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test-powershell-module.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: main 6 | 7 | # Allows you to run this workflow manually from the Actions tab. 8 | workflow_dispatch: 9 | 10 | # Allows the deployment workflow to call this workflow. 11 | workflow_call: 12 | inputs: 13 | versionNumber: 14 | description: 'The version number to use for the module. This should be in the format of "Major.Minor.Patch". e.g. "1.0.0". Future builds will increment from this version number. This input is optional. If not provided, the previous version numbers Patch will be incremented.' 15 | required: false 16 | type: string 17 | default: '' 18 | # Outputs required by the deployment workflow. 19 | outputs: 20 | powerShellModuleName: 21 | description: 'The name of the PowerShell module being built.' 22 | value: ${{ jobs.build-and-test.outputs.powerShellModuleName }} 23 | stableVersionNumber: 24 | description: 'The stable version number of the PowerShell module created by the build.' 25 | value: ${{ jobs.build-and-test.outputs.stableVersionNumber }} 26 | prereleaseVersionNumber: 27 | description: 'The full prerelease version number of the PowerShell module created by the build.' 28 | value: ${{ jobs.build-and-test.outputs.prereleaseVersionNumber }} 29 | prereleaseVersionLabel: 30 | description: 'The prerelease label of the PowerShell module created by the build.' 31 | value: ${{ jobs.build-and-test.outputs.prereleaseVersionLabel }} 32 | moduleArtifactName: 33 | description: 'The name of the module artifact created by the build.' 34 | value: ${{ jobs.build-and-test.outputs.moduleArtifactName }} 35 | deployFilesArtifactName: 36 | description: 'The name of the deploy files artifact created by the build.' 37 | value: ${{ jobs.build-and-test.outputs.deployFilesArtifactName }} 38 | 39 | env: 40 | powerShellModuleName: 'ScriptModuleRepositoryTemplate' 41 | powerShellModuleDirectoryPath: './src/ScriptModuleRepositoryTemplate' 42 | deployFilesDirectoryPath: './deploy' 43 | moduleArtifactName: 'ModuleArtifact' 44 | moduleArtifactDirectoryPath: './artifacts/Module' 45 | deployFilesArtifactName: 'DeployFilesArtifact' 46 | deployFilesArtifactDirectoryPath: './artifacts/deploy' 47 | 48 | jobs: 49 | build-and-test: 50 | runs-on: windows-latest # Use Windows agent so we can run Pester tests on Windows PowerShell 5.1 as well. 51 | outputs: 52 | powerShellModuleName: ${{ env.powerShellModuleName }} 53 | stableVersionNumber: ${{ steps.version-number.outputs.majorMinorPatch }} 54 | prereleaseVersionNumber: ${{ steps.version-number.outputs.majorMinorPatch }}-${{ steps.version-number.outputs.prereleaseLabel }} 55 | prereleaseVersionLabel: ${{ steps.version-number.outputs.prereleaseLabel}} 56 | moduleArtifactName: ${{ env.moduleArtifactName }} 57 | deployFilesArtifactName: ${{ env.deployFilesArtifactName }} 58 | steps: 59 | - name: Checkout the repo source code 60 | uses: actions/checkout@v4 61 | with: 62 | fetch-depth: 0 # Fetch all history so that GitVersion can determine the version number. 63 | 64 | - name: Display PowerShell version and OS details in case needed for troubleshooting 65 | shell: pwsh 66 | run: $PSVersionTable 67 | 68 | - name: Run spellcheck 69 | uses: streetsidesoftware/cspell-action@v5 70 | 71 | - name: Install GitVersion 72 | uses: gittools/actions/gitversion/setup@v0 73 | with: 74 | versionSpec: '5.x' 75 | 76 | - name: Get git metadata used to determine new version number 77 | id: git-version 78 | uses: gittools/actions/gitversion/execute@v0 79 | 80 | - name: Determine the new version number 81 | id: version-number 82 | shell: pwsh 83 | run: | 84 | [string] $newVersionNumber = '${{ steps.git-version.outputs.majorMinorPatch }}' 85 | [string] $prereleaseLabel = '${{ steps.git-version.outputs.preReleaseTag }}' 86 | 87 | [string] $manuallyProvidedVersionNumber = '${{ inputs.versionNumber }}' 88 | if (-not [string]::IsNullOrWhiteSpace($manuallyProvidedVersionNumber)) { 89 | Write-Output "Using manually provided version number '$manuallyProvidedVersionNumber'." 90 | $newVersionNumber = $manuallyProvidedVersionNumber 91 | } 92 | 93 | # The preReleaseTag is empty when building the default branch, so manually create a prerelease version number if needed. 94 | if ([string]::IsNullOrWhiteSpace($prereleaseLabel)) { 95 | [string] $dateTime = (Get-Date -Format 'yyyyMMddTHHmmss') 96 | $prereleaseLabel = 'CI' + $dateTime + 'SHA' + '${{ steps.git-version.outputs.shortSha }}' 97 | } 98 | # PowerShell prerelease labels can only contain the characters 'a-zA-Z0-9', so sanitize it if needed. 99 | $newVersionNumberPrereleaseLabel = $prereleaseLabel -replace '[^a-zA-Z0-9]', '' 100 | 101 | Write-Output "Setting step output variables 'majorMinorPatch=$newVersionNumber' and 'prereleaseLabel=$newVersionNumberPrereleaseLabel'." 102 | "majorMinorPatch=$newVersionNumber" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append 103 | "prereleaseLabel=$newVersionNumberPrereleaseLabel" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append 104 | 105 | # Suppress rules if needed: https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/using-scriptanalyzer 106 | - name: Run PowerShell linter with PSScriptAnalyzer 107 | shell: pwsh 108 | run: Invoke-ScriptAnalyzer -Path . -Recurse -EnableExit 109 | 110 | - name: Run Pester tests on Windows PowerShell to ensure backward compatibility 111 | shell: powershell 112 | run: | 113 | Write-Output "Pester version being used:" 114 | Import-Module -Name Pester 115 | Get-Module -Name Pester 116 | 117 | Write-Output "Running all Pester tests in the repo:" 118 | $pesterConfig = New-PesterConfiguration @{ 119 | Output = @{ Verbosity = 'Detailed' } 120 | Run = @{ Throw = $true } 121 | TestResult = @{ 122 | Enabled = $true 123 | OutputPath = 'test-results-windows-powershell-nunit.xml' 124 | } 125 | CodeCoverage = @{ Enabled = $false } 126 | } 127 | Invoke-Pester -Configuration $pesterConfig 128 | 129 | - name: Run Pester tests and generate code coverage report 130 | shell: pwsh 131 | run: | 132 | Write-Output "Pester version being used:" 133 | Import-Module -Name Pester 134 | Get-Module -Name Pester 135 | 136 | Write-Output "Running all Pester tests in the repo:" 137 | $pesterConfig = New-PesterConfiguration @{ 138 | Output = @{ Verbosity = 'Detailed' } 139 | Run = @{ Throw = $true } 140 | TestResult = @{ 141 | Enabled = $true 142 | OutputPath = 'test-results-pwsh-nunit.xml' 143 | } 144 | CodeCoverage = @{ 145 | Enabled = $true 146 | OutputPath = 'code-coverage-jacoco.xml' 147 | Path = 'src/' # Only include code coverage for the module's source code, not build or deployment scripts. 148 | } 149 | } 150 | Invoke-Pester -Configuration $pesterConfig 151 | 152 | - name: Add code coverage report to PR 153 | # Adding the code coverage report is not supported for manual workflow runs. 154 | if: github.event_name != 'workflow_dispatch' 155 | uses: madrapps/jacoco-report@v1.6.1 156 | with: 157 | paths: code-coverage-jacoco.xml 158 | token: ${{ secrets.GITHUB_TOKEN }} 159 | min-coverage-overall: 60 160 | min-coverage-changed-files: 60 161 | 162 | - name: Create the module artifact 163 | shell: pwsh 164 | run: | 165 | Write-Output "Reading in environment variables." 166 | [string] $moduleName = $Env:powerShellModuleName 167 | [string] $moduleDirectoryPath = $Env:powerShellModuleDirectoryPath 168 | [string] $moduleManifestFileName = $moduleName + '.psd1' 169 | [string] $moduleManifestFilePath = Join-Path -Path $moduleDirectoryPath -ChildPath $moduleManifestFileName 170 | [string] $moduleArtifactDirectoryPath = Join-Path -Path $Env:moduleArtifactDirectoryPath -ChildPath $moduleName 171 | [string] $newVersionNumber = '${{ steps.version-number.outputs.majorMinorPatch}}' 172 | 173 | Write-Output "Updating the version number of the module manifest file '$moduleManifestFilePath' to '$newVersionNumber'." 174 | Update-ModuleManifest -Path $moduleManifestFilePath -ModuleVersion $newVersionNumber 175 | 176 | Write-Output "Testing the module manifest file '$moduleManifestFilePath' to ensure it is valid." 177 | Test-ModuleManifest -Path $moduleManifestFilePath 178 | 179 | Write-Output "Copying the module files to the module artifact directory '$moduleArtifactDirectoryPath'." 180 | Copy-Item -Path $moduleDirectoryPath -Destination $moduleArtifactDirectoryPath -Exclude '*.Tests.ps1' -Recurse -Force 181 | 182 | [string] $moduleTemplateRepoFilesDirectoryPath = Join-Path -Path $moduleDirectoryPath -ChildPath 'TemplateRepoFiles' 183 | Write-Output "Copying the module template repo files '$moduleTemplateRepoFilesDirectoryPath' to the module artifact directory '$moduleArtifactDirectoryPath' verbatim, to ensure test files are included." 184 | Copy-Item -Path $moduleTemplateRepoFilesDirectoryPath -Destination $moduleArtifactDirectoryPath -Recurse -Force 185 | 186 | - name: Create deploy files artifact 187 | shell: pwsh 188 | run: | 189 | [string] $deployFilesDirectoryPath = $Env:deployFilesDirectoryPath 190 | [string] $deployFilesArtifactDirectoryPath = $Env:deployFilesArtifactDirectoryPath 191 | 192 | Write-Output "Copying the deployment files '$deployFilesDirectoryPath' to the deployment artifact directory '$deployFilesArtifactDirectoryPath'." 193 | Copy-Item -Path $deployFilesDirectoryPath -Destination $deployFilesArtifactDirectoryPath -Recurse -Force 194 | 195 | - name: Set the new version tag 196 | # Only run this step if we are doing a push (not a PR) to the default branch (e.g. main). 197 | if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) 198 | shell: pwsh 199 | run: | 200 | [string] $newVersionNumber = '${{ steps.version-number.outputs.majorMinorPatch}}' 201 | [string] $newVersionTag = "v$newVersionNumber" 202 | 203 | # To avoid a 403 error on 'git push', ensure you have granted your GitHub Actions workflow read/write permission. 204 | # In your GitHub repo: Settings > Actions > General > Workflow permissions > Read and write permissions 205 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#configuring-the-default-github_token-permissions 206 | 207 | Write-Output "Tagging commit with new version tag '$newVersionTag'." 208 | & git tag $newVersionTag 209 | & git push origin $newVersionTag 210 | 211 | - name: Upload module artifact 212 | uses: actions/upload-artifact@v4 213 | with: 214 | name: ${{ env.moduleArtifactName }} 215 | path: ${{ env.moduleArtifactDirectoryPath }} 216 | 217 | - name: Upload deploy files artifact 218 | uses: actions/upload-artifact@v4 219 | with: 220 | name: ${{ env.deployFilesArtifactName }} 221 | path: ${{ env.deployFilesArtifactDirectoryPath }} 222 | --------------------------------------------------------------------------------