This tutorial will demonstrate how to create a simple, single-function module using ModuleForge, with 1 pester test, and deploy via Azure DevOps Pipelines.
Pester
5.6+PSScriptAnalyzer
1.24+Microsoft.PowerShell.PSResourceGet
1.1.1+ModuleForge
1.2.0+Note: All the required modules are cross-platform compatible. You’re OS of choice does not matter.
Here is a code-snippet to help get you started
#Use the legacy PowerShell Get command to get PSResourceGet
Install-Module Microsoft.PowerShell.PSResourceGet
#Switch to PSResourceGet for the remainder
Install-PSResource -repository PSGallery -Name Pester,PSScriptAnalyzer,ModuleForge
New Repo
PSHelloWorld
hint: If you are unsure of how to clone a repository, copy the URI from the browser address bar and use it with the
git clone
command, e.g.git clone https://dev.azure.com/AzureDevOps-OrgName/AzureDevOps-ProjectName/_git/AzureDevOps-RepoName
New-MFProject
function to build the files out, as per the example belowsource
directory and a module config file moduleForgeConfig.xml
. The source directory shoud have a number of subfoldersAdd-MFAzureDevOpsScaffold
function to add 3 Azure Pipeline YML files, and a PR template to the .azuredevops
directory of your module folderNew-MFProject -ModuleName 'psGetHelloWorld' -description 'Another Hello World module'
Add-MFAzureDevOpsScaffold
Note: The Pipeline setup and build policies steps need to be repeated for any new repositories
New Pipeline
in the top right of the Pipelines window.Azure Repos Git
as the option for where is your code
PSHelloWorld
Existing Azure Pipeline YAML file
psScriptAnalyzerLintReport.yml
file
SAVE
Rename/Move
psScriptAnalyzerLintReport
to better identify it laterSelect Folder
should be named after the repository - PSGetHelloWorld
buildAndRelease
and pesterTesting
More Options
menu. Open it, and select 💡Branch policies
psScriptAnalyzer
pipeline we made in the previous sectionAutomatic
Optional
Script Analyzer Report
for easier visualisationPester Testing
pipeline we made in the previous sectionAutomatic
Required
Pester
for easier visualisationIf we did all the previous steps correctly, we should not need to do any more settings configurations for this repository.
feature/hello-world-function
.
functions
sub-directory of the source
directory
Get-HelloWorld.ps1
Hint: In VSCode you can create a branch by clicking on the current branch name in the bottom left corner, then in the dialog box, selecting
+Create New Branch
. VSCode will automatically switch you to the new branch
function Get-HelloWorld
{
<#
.SYNOPSIS
Return Hello World
.DESCRIPTION
Return Hello World. Allows you to overwrite the default name of world with
.EXAMPLE
Get-HelloWorld
#### OUTPUT
Returns "Hello World!"
.EXAMPLE
Get-HelloWorld -name 'James'
#### OUTPUT
Returns "Hello James!"
#>
[CmdletBinding()]
PARAM(
#Name param, specify to override the default of World
[Parameter()]
[string]$Name = 'World'
)
begin{
#Return the script name when running verbose, makes it tidier
write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
#Return the sent variables when running debug
Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
}
process{
"Hello $Name!"
}
}
functions
sub-directory of the source
directory
Get-HelloWorld.Tests.ps1
Tests
is in the correct, capital-case format for invoke-pester
to work.. $PSCommandPath.Replace('.Tests.ps1','.ps1')
BeforeAll{
#Load The Function File
. $PSCommandPath.Replace('.Tests.ps1','.ps1')
}
Describe "Get-HelloWorld Default Parameters" {
BeforeAll {
$hello = Get-HelloWorld
}
It 'Should not be null or empty' {
$hello |Should -Not -BeNullOrEmpty
}
It 'Should contain "Hello World!"' {
$hello |Should -Be 'Hello World!'
}
}
Describe "Get-HelloWorld Custom Name" {
BeforeAll {
$hello = Get-HelloWorld -name 'James'
}
It 'Should not be null or empty' {
$hello |Should -Not -BeNullOrEmpty
}
It 'Should contain "Hello James!"' {
$hello |Should -Be 'Hello James!'
}
}
Invoke-Pester '.\source\functions\Get-HelloWorld.Tests.ps1'
feat
commit prefix and a relevant comment
feat: added get-helloWorld function
test
commit prefix
test: added Pester testing for get-helloWorld
Hint: If you are not sure how to commit in VSCode, switch to the
Source Control
item in the left-most menu. You will need to stage each file by clicking the small plus sign next the the filename. Enter the commit message into the text box labelled changes, when ready, hit commit. Once all your files are committed, you can publish your branch with thePublish Branch
button that replaces theCommit
button
Note: Don’t forget to checkout the Main branch on your local working directory before adding any more code.
X
to help determine your next version, and to make it easier to review later.Merge Pull Request
If you want to explore your pester results in more details before a merge, you can click on
checks
and review the pipeline results in details. You can also check out the Code-Coverage results that get added as part of the Pester Test step.
Pipelines
menuBuild and Release
pipeline associated to your repositoryRun Pipeline
option, a menu will appear. Fill out the fields appropriately and then choose run workflow
none
prerelease
ModuleForge
version should be stableIf you want to
manually
download and install your new module, you can download the nupkg from the package feed, rename the extension to.zip
and uncompress to find your module.
In your PowerShell terminal with PSResourceGet ready to go, you can register a new repository. You will only need to register the repository once.
User Settings
menu next to your Profile headshot in the top right of Azure DevOpsPackages: Read
accessMicrosoft.PowerShell.SecretManagement
module). This is the recommended approachYou can read more about the next steps here
To register with a credential, see the example code below
### Create a credential object
### Your username will be your Azure DevOps account email
### The password will be the PAT you created
$azdCredential = Get-Credential
#use the register-psResourceRepository commandlet to register
#This is easier to do with splatting
#Don't forget to swap out the azdCredential with your actual Azure account
$splat = @{
Name = 'myAzureDevopsFeed'
Uri = 'https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/nuget/v3/index.json'
Trusted = $true
}
#Splat it in
Register-PSResourceRepository @splat
#Once registered, you can use it in a similar fashion to PSGallery
#Don't forget to specify the -PreRelease flag
Find-PSResource -Name psGetHelloWorld -Prerelease -Repository myAzureDevopsFeed -Credential $azdCredential
To register with Microsoft.PowerShell.SecretManagement
, first, create a new secret in your secret vault. The username should be your Azure DevOps account email, and the password the PAT you created
### Create a pointer to your secret and vault
### Your username will be your AZD email
### The password will be the AZD PAT you created
#You will need to make sure that PSResourceGet module is imported first
$credentialInfo = [Microsoft.PowerShell.PSResourceGet.UtilClasses.PSCredentialInfo]::new('VaultName', 'SecretName')
#use the register-psResourceRepository commandlet to register
#This is easier to do with splatting
#Don't forget to swap out the azdCredential with your actual azure account
#
$splat = @{
Name = 'myAzureDevopsFeed'
Uri = 'https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/nuget/v3/index.json'
Trusted = $true
CredentialInfo = $credentialInfo
}
#Splat it in
Register-PSResourceRepository @splat
#Once registered, you can use it in a similar fashion to PSGallery
#Don't forget to specify the -PreRelease flag
#Because we supplied a CredentialInfo pointer, Find-PSResource will automatically open our vault to retrieve the secret when required
Find-PSResource -Name psGetHelloWorld -Prerelease -Repository myAzureDevopsFeed
A note on V3 nuget feeds and PSResourceGet. Wildcard searching is not supported at this time, you will need to know the exact name of your module for find and install commands to work.
In this tutorial, we created a new repository, added our ModuleForge scaffolding, created a new PowerShell function + test, performed a review and unit test, and released it as a PreRelease into our private Azure DevOps Packages feed for consumption. We effectively made a CI/CD PowerShell Pipeline using Github Actions + ModuleForge, and published a single-function module.