Contents

Publish a PowerShell Module using Github Actions

How to publish a PowerShell Module to the PSGallery by using GitHub Actions


A step-by-step tutorial on how to create a PowerShell Module and publish it to the PSGallery by using GitHub Actions

You might be like me. Use PowerShell daily. Oneliners most of the time. For more advanced tasks you write a script and perhaps schedule those by using Task Scheduler,
the SQL Server Agent or something similar. But creating a PowerShell module? Publishing it to the PowerShellGallery? Crazy!

I’m by no means an expert but I got it working. So if you are interested then try to follow along. You might learn something new. I sure did! 😎

Lets create a PowerShell Module and publish it to the PSGallery using GitHub Actions!

πŸ“‹ Prerequisites


[βœ”] GitHub Account

I’m hosting the source code of my module in a Git repository on GitHub. If you are interested in doing the same you should create a GitHub account if you don’t already have one. They have a free plan available and the process is straight forward. Just go to their site to sign up.

[βœ”] Git in PowerShell

The posh-git package that provides integration between Git and PowerShell. It will display Git status summary information in your PowerShell prompt and provides support for tab completion when working with Git commands, branches, paths, etc. I want it πŸ€—

Follow the process as described on their site on how to install it.

[βœ”] Pester

Pester is available on Windows 10 or later by default.
You can check whether you have this module installed by running:

1
Get-Module Pester -ListAvailable

The 3.4.0 version ships as part of Windows 10 and is a bit problematic. To avoid headaches I would suggest updating it right away. Unfortunately updating the module using the Update-Module cmdlet won’t work. So you will have to install the latest version of Pester side-by-side using the follow tactic. Open PowerShell as Admin and run the following command:

1
Install-Module -Name Pester -Force -SkipPublisherCheck

Now if you run the following command again:

1
Get-Module Pester -ListAvailable

It will display both the shipping and the latest version of Pester.

[βœ”] Visual Studio code

I highly suggest using a code editor. My editor of choice is Visual Studio Code.

To install VS Code run:

1
winget install Microsoft.VisualStudioCode
the winget tool
The winget tool is the client interface to the Windows Package Manager service. You can use it to search for and/or install applications. It also has some other commands in store that allow you to display installed packages, export/import, validate and a bunch of more useful stuff. Once you have it installed simply run winget to see all commands that are available. You can read more about winget here

And of course if you prefer a different editor yourself than you can use that one instead.

πŸ“‚ Prepare your Git repository


I hope you have read through the prerequisites and signed up for GitHub because GitHub is where we start. We are going to set up the Git repository (repo) for our module.

Click on your profile in the top right of the page. A menu will open. Click on Your repositories. We are going to create a new repository so click on New.

Provide a meaningful name for your repository. I provided the name of the module I created. Optionally you can give a description for you repository as well.

Another decision you will have to make is whether you want your repository publicly visible or not. Set your repo to either:

  • Public: Anyone on the internet can see this repository. You choose who can commit.
  • Private: You choose who can see and commit to this repository.

You can always change this in the settings of your repository later as well.

Additionaly you can select whether you want to initialize your repository with a README file, a .gitignore file and a license.

Once you have provided all the necessary information press the button Create repository at the bottom of the page.

🎈 Congrats on your Repo! πŸŽ‰

Now go back to the page that lists all your repositories and open the repository that you have just created. There should be a green button named Code. Press that button and copy the provided URL. Open PowerShell on your machine and browse to your working folder. For this example I am using my home directory.

1
cd $home

clone your Repository by running the following command:

1
git clone https://github.com/<username>/<repository>.git

replace the URL with the one you have copied earlier. Or just replace the tokens with your own details.

There you go! You have your repository locally now. Awesome! I think? A bit empty though..? Hmm, lets work on that!

πŸ“‚ Prepare your PowerShell repository


// describe what it will look like

e.g.

  • design patterns / folder structure
  • run Tree utility to show what mine looks like
  • describe what comes later. E.g. .Github workflow, maybe .vscode/.devcontainer? (or out of scope?)

First we will have to think about how we design our repository. We want an ordered folder structure. I have looked at what others have done and most of them have a similar structure as the one that I have used below.

the tree command

The tree command displays the directory structure of a path or of the disk in a drive graphically. It’s a very useful tool to quickly get a grasp on how the folder structure is.

Read more about this command here

For the example below I ran the command: tree . /F. Where . is the current directory I’m in and /F to display files as well.

In the root of the repository I have a README and License file. The README file is an important part of your repository. Actually, mine should get some love! πŸ˜…

The .github and build_scripts folders can be ignored for now. We will cover this later once we start working on our pipeline.

For now create a directory with the name of your module. This is where we are going to create our module and manifest files for our PowerShell module. Within the directory we just created, create another folder with the name functions. This is where we are going to place all our functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
JTAZ
β”‚   LICENSE
β”‚   README.md
β”‚   Unit.Tests.xml
β”‚
β”œβ”€β”€β”€.github
β”‚   └───workflows
β”‚           main.yml
β”‚
β”œβ”€β”€β”€build_scripts
β”‚       build.ps1
β”‚
└───jtAz
    β”‚   jtAz.psd1
    β”‚   jtAz.psm1
    β”‚   jtAz.Test.ps1
    β”‚
    └───functions
            Get-jtAzProviderNamespace.ps1
            Get-jtAzResourceTypes.ps1
            Get-jtAzTemplate.ps1

Now that we have a skeleton of our PowerShell repository lets commit and push these changes to our repository on GitHub. To do this run the commands:

1
2
3
git add .
git commit -m "first commit"
git push origin main

Now if you check your repository on GitHub your code will be there as well.

⚑ Functions!


When you are creating a PowerShell module you are going to need to have one or more scripts/functions first.

If you are new to functions have a look at this documentation. It might sound intimidating at first but it is a really straight forward process. Maybe you already have a script available that you might want to turn into a function (because you are using running that script quite often now that you think of it πŸ€”). Give it a shot! And if you don’t have anything existing yet try to come up with something and try doing it with PowerShell. I found myself looking at the Azure ARM template documentation quite often but usually need to only have a quick look for reference. So I thought maybe I can create a function that will just return the ARM (or Bicep) template from the docs onto my terminal window. So it doesn’t have to be revolutionary just try something that sounds useful to you. Dive in. That is how we learn πŸ‘

Once you have created your function save them in the .\<modulename>\functions\ folder. Now we can have a look what that psm1 file is all about. πŸ±β€πŸ

πŸ“¦ The module file


Next up we have our script module file. As described in the Microsoft documentation a module is:

“A module is a set of related Windows PowerShell functionalities, grouped together as a convenient unit (usually saved in a single directory). By defining a set of related script files, assemblies, and related resources as a module, you can reference, load, persist, and share your code much easier than you would otherwise.”

Create a file with the name of your module and save it with the .psm1 extension in the folder that also has your module name. Now we could have saved the code of our function in this module file and that would have been a valid and working approach. However, as you can imagine that might become quite cumbersome once you start adding additional functions and/or have to modify them. So instead of doing this we are going to use the functions subfolder, dot source each of our functions and export them.

Save the following code in your module file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Set-StrictMode -Version Latest

$functions = Get-ChildItem -Path $PSScriptRoot\functions\*.ps1

foreach ($import in $functions) {
    try {
        Write-Verbose "Importing $($import.FullName)"
        . $import.FullName
    } catch {
        Write-Error "Failed to import function $($import.FullName): $_"
    }
} #foreach

foreach ($file in $functions) {
    Export-ModuleMember -Function $file.BaseName
} #foreach

dot sourcing the functions will load the functions into memory without executing the code.

πŸ“„ The manifest file


We are also going to create a manifest file (the one with the .psd1 extension). This is a file that will contain a hashtable with keys and values that describes everything about the module. Think prerequisites, what scripts need to be processed, metadata, versions, etc. We can simply create this file using the New-ModuleManifest PowerShell cmdlet.

Checking the help for this cmdlet will give you all the details:

1
Get-Help New-ModuleManifest -Full

Now that we know what we can specify. Lets create one ourselves. Replace SomeModule with the name of your module and replace the values with your own. You can provide additional parameter if you want to.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ModuleName = 'SomeModule'
$props = @{
    Path = "$ModuleName.psd1"
    RootModule = "$ModuleName.psm1"
    GUID = "$(New-Guid)"
    Author = 'Jeroen Trimbach'
    CompanyName = 'N/A'
    Description = "beep boop I'm backing up"
    ModuleVersion = '9.9.9' # Leave as is!
    FunctionsToExport = @('<FunctionsToExport>') # leave as is!
    CmdletsToExport = @()
    VariablesToExport = @()
    AliasesToExport = @()
}
New-ModuleManifest @props

((Get-content -path ".\$($ModuleName).psd1") -replace '9.9.9','<ModuleVersion>') |
    Set-Content -path ".\$($ModuleName).psd1"

Have a check at the content of your newly created file. Uncomment keys that seem useful to you and provide values where required. We have not given any function names and module version intentionally. We are going to replace these in our actions workflow because as mentioned earlier whenever we create a new function we want our module to pick that up as well without having to edit the manifest file ourselves. This same idea applies to the ModuleVersion key.



πŸ–Ό PowerShellGallery


The plan is that we are going to publish our module to the PowerShell Gallery. This is a free website maintained by Microsoft.

Register on the website. Once you have an account in the Gallery we want to create a API key. Because whenever we want to publish any package to the PowerShell Gallery we are going to provide this key.

Click on your Profile. It will show a button named API Keys. This will take you to the API Keys page. Click on Create. Provide a name for your key and give an amount of days until the key will expire. The default is 365 days. Now click on Create again at the bottom of the page. Copy the key that is displayed on the screen.

Now we are going to store this key in a safe location from where we can use it in our next steps. Go to your Github repository and click on settings. There will be a menu item called Secrets. This will take you to the Actions secrets page. Click on New repository secret at the top right of the page. Provide a name for your secret (e.g. PSGALLERYAPIKEY) and provide your key. Click on save.

Now we are completely set. Time for Actions! πŸ’₯

πŸ”© GithHub Actions


Oh, one more step. Remember the values we were going to replace in our manifest file?
Lets do that now. Create a directory in the root of your repository with the name build_scripts or something similar if you haven’t already done so. Create a file in there with the name build.ps1. Set the content of that file to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$Global:ErrorActionPreference = 'Stop'
$Global:VerbosePreference = 'SilentlyContinue'

$buildVersion = $env:BUILDVER
$manifestPath = "./jtAz/jtAz.psd1"
$publicFuncFolderPath = './jtAz/functions'


if (!(Get-PackageProvider | Where-Object { $_.Name -eq 'NuGet' })) {
    Install-PackageProvider -Name NuGet -force | Out-Null
}
Import-PackageProvider -Name NuGet -force | Out-Null

if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') {
    Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
}

$manifestContent = (Get-Content -Path $manifestPath -Raw) -replace '<ModuleVersion>', $buildVersion

if ((Test-Path -Path $publicFuncFolderPath) -and ($publicFunctionNames = Get-ChildItem -Path $publicFuncFolderPath -Filter '*.ps1' | Select-Object -ExpandProperty BaseName)) {
    $funcStrings = "'$($publicFunctionNames -join "','")'"
} else {
    $funcStrings = $null
}

$manifestContent = $manifestContent -replace "'<FunctionsToExport>'", $funcStrings
$manifestContent | Set-Content -Path $manifestPath

Now in your Github repository open the Actions menu. Click on the New workflow button. It will prompt you to choose a workflow template. We will go for set up a workflow yourself. This will open up a file called main.yml in <repo>/.github/workflows/. Replace the contents of that file with the following workflow:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the main branch
  push:
    branches: [ main ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  publish-to-gallery:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Build and publish
      # API key generated in PSGallery
        env:
          NUGET_KEY: ${{ secrets.PSGALLERYAPIKEY }}
          BUILDVER: '1.0.0'
        shell: pwsh
        run: |
          ./build_scripts/build.ps1
          Publish-Module -path ./jtAz -NuGetApiKey $env:NUGET_KEY -Verbose          

Additionaly it might be a good idea to add a job that will run tests on your code first. For PowerShell using Pester is an excellent way to do that. Especially since it is automatic. I will update this post later to include some unit tests and how you can use them in your workflow.

Now click on start commit. Select your workflow and Run workflow! Now it’s time to sit back and relax 🍿

Your workflow will run and if all goes well it will show a green checkmark βœ… Which also means that you now have your module published to the PowerShell Gallery. They will also send you mail upon succes.

If you get a failure message well then you know what that means.. troubleshoot time. This may or may not have happened to me a few times.. πŸ™‚

πŸ’‘ Conclusion


In this post we have learned how we can create a PowerShell module using functions that we have stored in separate ps1 files. We have started using a bit of git. And we know now how we can publish our modules to the PS Gallery by using a GitHub Actions workflow.

There is still a lot of room for improvement. We definitely need some tests in there else we are stuck doing that manually and we don’t want that.

If you want to learn more about PowerShell then I highly recommend reading through the two books that I have mentioned below. The books are very well written and I have learned a lot from them. Whenever I want to do something in PowerShell but not sure what the correct approach is I try to search in those books first. I will pick up something new most of the times as well.

  • Learn PowerShell in a Month of Lunches - Don Jones, Jeffery Hicks
  • Learn PowerShell Scripting in a Month of Lunches - Don Jones, Jeffery Hicks

Good luck learning and have a nice day! πŸ‘