This is a proof of concept on how you can develop, build, and package PowerShell projects within GitHub leverage GitHub flow and techniques and Azure DevOps Artifacts.
This project aims to satisfy several goals in its design:
- Building and releasing PowerShell scripts and modules as software artifacts
- Creating repeatable processes that can be reviewed and audited
- Integrating into native PowerShell workflows
- Support phased deployment and rollback of installed scripts and modules
- Minimizing management of additional in-network resources
- Secure secret management
As of October 2021, the PowerShellGet 3.0 beta with NuGet v3 support has a couple of known issues using NuGet-based GitHub Packages as a PowerShell repository:
This only impacts PowerShell projects; developers on dotnet
projects leverage nuget
and dotnet nuget
CLIs with NuGet GitHub Packages repositories as expected:
- GitHub Packages: Private NuGet Packages via GitHub Actions
- Example script for publishing a PowerShell module to the NuGet GitHub Package Registry
- How To Setup A Github Actions Pipeline For Publishing Your PowerShell Module (to PowerShell Gallery)
- Consuming a NuGet package from GitHub Packages
Azure DevOps Artifacts is the only managed PowerShell repository solution with NuGet v2 support recommended by Microsoft capable of supporting private PowerShell repositories.
GitHub-hosted runners have support for PowerShell 5.1 and/or 7.x depending on the virtual machine type and shell used:
Virtual machine \ PowerShell version | 5.1 | 7.x | Per-minute rate | End of life |
---|---|---|---|---|
macos-10.15 | pwsh |
$0.08 | ||
macos-11 | pwsh |
$0.08 | ||
ubuntu-18.04 | pwsh |
$0.008 | ||
ubuntu-20.04 | pwsh |
$0.008 | ||
windows-2016 | powershell |
pwsh |
$0.016 | March 15, 2022 |
windows-2019 | powershell |
pwsh |
$0.016 | |
windows-2022 | powershell |
pwsh |
$0.016 |
For differences and history around PowerShell 5.1 (Desktop) and 7.x (Core), read more.
For information on how GitHub-hosted runners are billed, read more.
For confirming the version of PowerShell
The following action workflow will show both PowerShell 5.1 and 7.x are installed on windows-*
runners:
on:
workflow_dispatch:
jobs:
check-powershell-versions:
strategy:
matrix:
runner:
- windows-2016
- windows-2019
- windows-2022
runs-on: ${{ matrix.runner }}
steps:
- run: |
Get-Host
shell: powershell
- run: |
Get-Host
shell: pwsh
resulting in:
Name : ConsoleHost
Version : 5.1.20348.230
InstanceId : b6a8f124-9444-4c21-9af8-5299d854b274
UI : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture : en-US
CurrentUICulture : en-US
PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
DebuggerEnabled : True
IsRunspacePushed : False
Runspace : System.Management.Automation.Runspaces.LocalRunspace
and
Name : ConsoleHost
Version : 7.1.5
InstanceId : 58ae196a-c589-4e2f-aa78-c1b76f69cf1e
UI : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture : en-US
CurrentUICulture : en-US
PrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
DebuggerEnabled : True
IsRunspacePushed : False
Runspace : System.Management.Automation.Runspaces.LocalRunspace
This repository is a proof of concept and it is evolving as we learn from others and exercise ideas. One of those areas is the relation of repository design and building artifacts.
- Invoke-Automation "PowerShell Scaffolding – How I build modules"
- PowerShell Explained "PowerShell: Adventures in Plaster"
- Rambling Cookie Monster "Building a PowerShell Module"
- Adam the Automator "Understanding and Building PowerShell Modules"
This repository has been designed to support multiple modules and/or scripts:
├── docs # documentation, images, and other assets
│ └── assets
│
├── modules # directory containing modules, will publish on merge to main
│ └── PSHello
│ ├── Private # directory containing module's private functions that should not be exported
│ ├── Public # directory containing module's public functions that should be exported
│ └── en-US # directory containing module's help docs (en-US locale)
│
├── scripts # directory containing scripts, will publish on merge to main
│
└── tests # directory containing tests for modules and scripts
Creating new scripts
Scripts are contained within the scripts
directory and are published individually when merged to the main
branch.
Scripts should be created using the New-ScriptFileInfo
cmdlet and include basic information like:
$scriptFileInfo = @{
Path = "./scripts/PSHelloWrapper.ps1"
Version = "0.0.1"
Author = "andyfeller@github.com"
Description = "Simple wrapper around PSHello module"
}
New-ScriptFileInfo @scriptFileInfo
Creating new modules
Modules are contained within the modules
directory and are published individually when merged the main
branch.
Modules should be created using the New-ModuleManifest
cmdlet and include basic information like:
$moduleManifest = @{
Path = "./modules/PSHello/PSHello.psd1"
Version = "0.0.1"
Author = "andyfeller@github.com"
Description = "Simple module to print Hello world!"
}
New-ModuleManifest @moduleManifest
The modules and scripts contained in the above directories will be released whenever changes are merged to the main
branch. In order to make it easier for contributors, modules and scripts will be released with the revision portion of
their version set to the action run number before publishing. Ideally, contributors would update the version whenever
changes are made, but to err is human.
Want to give special thanks to @agazoth's "Increment Build and Version in PSake" article as it highlighted the use of Update-ModuleManifest
and Update-ScriptFileInfo
to update version numbers in the release process!
The following is how a maintainer would setup the necessary Azure DevOps Artifacts feed and tokens for private PowerShell repository this proof of concept is based on:
-
Create Azure DevOps Artifacts feed and personal access token (PAT) for CI
-
Create GitHub Actions secret with Azure DevOps Artifacts PAT
-
Ensure strong cryptography (TLS 1.2) is enabled for Azure DevOps
As of Febuary 2020, Azure DevOps Artifacts requires hosts have TLS 1.2 supported, which was a response to changing PCI compliance standards.
To confirm whether TLS 1.2 supported is enabled
[Net.ServicePointManager]::SecurityProtocol Tls, Tls11, Tls12
To enable TLS 1.2 support
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' -Name SchUseStrongCrypto -Value 1 -PropertyType 'Dword' -Force | Out-Null Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' -Name SystemDefaultTlsVersions -Value 1 -PropertyType 'Dword' -Force | Out-Null If ([System.Environment]::Is64BitOperatingSystem) { Set-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0.30319' -Name SchUseStrongCrypto -Value 1 -PropertyType 'Dword' -Force | Out-Null Set-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0.30319' -Name SystemDefaultTlsVersions -Value 1 -PropertyType 'Dword' -Force | Out-Null }
Afterwards, close and reopen PowerShell terminal.
-
Install SecretManagement and SecretStore modules from the PowerShell Gallery to securely hold credentials:
Install-Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore -Repository PSGallery
-
Create secret vault for storing Azure DevOps Artifacts credentials:
Register-SecretVault -Name powershell-poc -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
-
Create secrets in secret vault for authenticating to Azure DevOps Artifacts:
Set-Secret -Name powershell-poc-username Set-Secret -Name powershell-poc-password $credential = New-Object -TypeName PSCredential -ArgumentList (Get-Secret powershell-poc-username), (Get-Secret powershell-poc-password) Set-Secret -Name powershell-poc-credential -Secret $credential
You will be prompted for a password to secure a secret vault the first time a secret is saved:
Creating a new powershell-poc vault. A password is required by the current store configuration. Enter password: ******************** Enter password again for verification: ********************
-
Register PowerShell repository using the read-only packages PAT
$psRepository = @{ Name = "powershell-poc" SourceLocation = "https://pkgs.dev.azure.com/andyfeller/powershell-poc/_packaging/powershell-poc/nuget/v2" ScriptSourceLocation = "https://pkgs.dev.azure.com/andyfeller/powershell-poc/_packaging/powershell-poc/nuget/v2" InstallationPolicy = "Trusted" Credential = (Get-Secret powershell-poc-credential) } Register-PSRepository @psRepository Get-PSRepository
-
Install and invoke
PSHello
module:Install-Module PSHello -Repository powershell-poc -Credential (Get-Secret powershell-poc-credential) Write-PSHelloWorld
-
Install and invoke
PSHelloWrapper
script:Install-Script PSHelloWrapper -Repository powershell-poc -Credential (Get-Secret powershell-poc-credential) Get-InstalledScript | Format-List *