Assuming you have a few PowerShell functions that you would like to gather into one package to easily distribute, you will want to make a module. This will allow you to load your functions on a specific computer and then call the functions without any further effort on your part.
At work, I use Azure DevOps so I wrote this to take my code and bundle the functions into a module, test the module is operational, and save the module as an artifact that can be downloaded. If you don’t want to use ADO, you can adapt the code as necessary.
I didn’t figure this all out on my own, here’s where I learned all this-
These instructions are an amalgamation of those listed in the links and simplified for me.
- Make folder structure
- Add your functions and tests
- Make configuration file, from sample
- Copy build file, from GITHUB
- Copy Pester Tests, from GITHUB
- Copy YAML file to define build process, from GITHUB
- Create pipeline in Azure DevOps and output artifact
- Review code to download new artifact
To hold the sample code, I have a GITHUB repo with sample files-
https://github.com/chinkes5/PowerShellModule
Step 1
First is to organize what you have. The folder structure and a few key file names will drive a lot of how the module is found and used. All the folders described are relative to the same root folder in your Azure DevOps repository.
The functions you have should be named with one file having a single function and the name of the file the same as the function within. Those that are public, that is functions for general consumption, should be put in a folder named ‘Public’. If you have private functions that you don’t want generally exposed, you can make a folder ‘Private’ as well.
Make a folder ‘Tests’. There will be a few standard tests to make sure the module can be built. If you have written any Pester tests for your functions, add them here as well. The expected format will be [function-name].test.ps1
Step 2
In the root folder, make a json configuration file called ‘module-config.json’. Here is a sample to follow:
{
"name": "My_PS_Module",
"description": "PowerShell module for use by my team for standard operations",
"Author": "John Chinkes",
"CompanyName": "Your Company Name",
"CopyrightStartYear": 2024,
"GUID": "your-guid-value",
"PowerShellVersion": "5.0"
}
The build will use this information to make a PSM1, PSD1, and NuSpec files.
Step 3
You will want a build file to assemble all the functions and create your manifest file. Use the following, calling it ‘build.ps1’:
Here’s the file in GITHUB- https://github.com/chinkes5/PowerShellModule/blob/main/build.ps1
The values you put in the config file will be found and swapped out by this process. Also, the names of all the functions will be added. When you call the module, it will read the public and private folders to expose the public functions. To add new functions to your module, just re-run this build file and they will be added!
If you want an explanation of what’s going on inside this file, see this post.
Step 4
Optionally, make Pester tests for your functions and the module. How to make Pester tests is outside the scope of this document but the following basic test is recommended:
Here’s the file in GITHUB- https://github.com/chinkes5/PowerShellModule/blob/main/test.ps1. It will print out several paths so you can see where it’s working and then invoke-pester on the ‘Tests’ folder. The output will be an xml file that Azure DevOps will display with the build.
Save this file as test.ps1 in the root of your module folder.
Step 5
The output of the pipeline will not actually be a module, but a NuGet file that can be loaded to on the destination computer.
At noted at the beginning, I’m working with Azure DevOps for my CI/CD tool. Here is a sample YAML file to build the module. Please be careful with the spacing and formatting when you copy! You will want to swap out the path to your code in the repository. At the end of the file, swap out the name of your feed in Azure DevOps Artifacts.
Here’s the file in GITHUB- https://github.com/chinkes5/PowerShellModule/blob/main/module.yaml. Save this as Module.YAML in the root of your module directory.
Step 6
Create the pipeline to output a new module to your Azure DevOps Artifacts. You will want to check in all the code and structure you have created above.
- Sign in to Azure DevOps:
- Navigate to Pipelines:
- In the left-hand menu, click on Pipelines.
- Click on New pipeline.
- Select the Repository:
- Choose the location of your code (e.g., Azure Repos Git, GitHub, etc.).
- Select the repository where your YAML file is located.
- Configure the Pipeline:
- In the Configure step, select Existing Azure Pipelines YAML file.
- Choose the Module.YAML file from your repository.
- Review and Run:
- Review the YAML file content.
- Click on Save and run to start the pipeline.
After the pipeline has run, you should be able to check for a new artifact with your module.
Step 7
You will need a PAT with permissions to read and download packages or set the permissions to allow for public consumption. Use this code to register your Azure DevOps Artifacts on a destination computer, swapping out your PAT, a name for your repo, and the exact URL to your DevOps Artifact:
$patToken = "Your PAT goes here" | ConvertTo-SecureString -AsPlainText -Force
$credsAzureDevopsServices = New-Object System.Management.Automation.PSCredential("username", $patToken)
$srePackageSource = "Name you call your repo"
packageSourceList = Get-PackageSource
if ($packageSourceList.Name -notcontains $srePackageSource) {
Write-Output "Adding package source to be able to get $srePackageSource..."
$regParam = @{
Name = $srePackageSource SourceLocation = "https://pkgs.dev.azure.com/[collection]/_packaging/youPathTo/nuget/v2"
PublishLocation = "https://pkgs.dev.azure.com/[collection]/_packaging/ youPathTo/nuget/v2"
InstallationPolicy = 'Trusted'
Credential = $credsAzureDevopsServices
Verbose = $true
}
Register-PSRepository @regParam
}
else {
Write-Output "Package source of $srePackageSource found."
}
$psRepoList = Get-PSRepository
if ($psRepoList.Name -notcontains $srePackageSource) {
Write-Output "Registering package source $srePackageSource..."
$pakParam = @{
Name = $srePackageSource
Location = "https://pkgs.dev.azure.com/[collection]/_packaging/youPathTo/nuget/v2"
ProviderName = 'NuGet'
Trusted = $true
SkipValidate = $true
Credential = $credsAzureDevopsServices
}
Register-PackageSource @pakParam
}
else {
Write-Output "PowerShell repository $srePackageSource found."
}
Then use PowerShellGet to find and download the module, note the reuse of the credential from prior script. Swap out the name used for your repo and the name of the module in this code:
Write-Output "Finding Your Module..."
Find-Module -Name 'Name of Module' -Repository 'Name you call your repo' -Credential $credsAzureDevopsServices -Verbose x
Write-Output "Downloading Your Module..."
Save-Module -Name 'Name of Module' -Repository 'Name you call your repo' -Path ($env:PSModulePath -split ';')[1] -Credential $credsAzureDevopsServices -Verbose
Write-Output "Importing Your Module..."
Import-Module -Name “Name of Module” -Force -Scope Global
You now have a module and downloaded it on a destination computer!