Building a new PowerShell module from scratch

Recently I’ve been working on a new project and I found my self in the situation where I had to create a PowerShell module. Normally I would use the templates we have internally at the company I work for, but this project was for a personal project so I couldn’t use it. This allowed me to check out what’s available now and in this blogpost I want to share with you how I quickly set up a new PowerShell module for this project.

I was working on this project because of PSConfEU 2024 which will be in June. This week the news came that I am selected as a speaker for this conference again. If you are interested in PowerShell I can really recommend considering going to the conference! But now back to how I made the module.

Where to start?

You could set up a module completely by yourself following the guides on the internet, but this requires some tedious work, so I opted to use the PSModuleDevelopment module to create a scaffolding for my module. You can easily start using this by installing the module from the PSGallery.

Install-Module PSModuleDevelopment

The module contains templates for many different PowerShell project. You can for example create an Azure Function or DSC Template, I decided to use the template “MiniModule”. This module is a simple PowerShell module for functions. It has build in tests and build pipelines so I only have to worry about writing my code. By executing the following command I set up the template.

Invoke-PSMDTemplate MiniModule

After you run the command it will ask you for the name of the module and then create everything needed.
I knew about this project for a while already as I was working on a contribution for this project. After my talk about using PSScriptAnalyzer and Pester to test Code Quality last year at the PSConfEU I was asked if I could add those tests to the module too. So recently this has been added to the development build for the module and eventually will be added in the releases too. But don’t worry it already contains PSScriptAnalyzer tests, it’s just that their output is not very user friendly.

What does the module do?

Time to talk about why I actually wanted to create a module. I found myself in a situation where I had a list of multiple locations but I had to figure out in which country these locations where. To do this I wanted to use a service like OpenStreetMaps to look up the locations and get the information back. When looking at the available modules I couldn’t find one which suited me, especially because I also wanted to have the option to search based on coordinates. There was one module which looked promising but this was created to work in conjunction with a specific piece of software and the installation instruction where very confusing. So I decided to create one of my own. To make it a bit more useful for other people I decided to make the module in a way where it could also use Google Maps and Bing Maps to look up locations.

So in the newly created module I started filling the folder with the name of my module.

This template has two main folder in the module folder. In “functions” you can place all functions which need to be accessible for people using the module. In the folder “internal” you can place functions and scripts that are used inside your module.

Do note that you still need to change module manifest yourself to add the exported functions, this isn’t done automatically (yet) for as far as I could see.

As you see in my module I created one main function people can use. This function has a “Provider” attribute where you can select which service to use (either Open Street Maps, Google Maps or Bing Maps). All three of these services have API endpoints to search based on a query and reverse geocode based on coordinates. In the documentations of these services I looked up how to use them and created the functions Find-GeoLocation<Name>.ps1. You might notice that it doesn’t say Open Street Maps but instead says Nominatim. This is because the api I use it provided by this add on for Open Street Maps, therefore internally I called it this.

The reason I wanted to use Open Street Maps is because this service can be used without an API Key. For both Google and Bing maps you need to first request an API Key before you can use this service. This can be a big hurdle for people who want to easily use this function.

Now the function has output but all of these services provide their output in different formats. This is why I also created the Convert functions. For every provider there is a function which takes the data from that respective output and converts it to the output format I want.

I like data

I am planning on maintaining this module, but to do this properly it’s useful to know how much it’s used and which providers are used the most. I was inspired by a talk at PSConfEU last year about adding telemetry to your PowerShell modules. So I decided to also add this to this module. As shown in the presentation there is a nice PowerShell module called Telemetryhelper. In the talk and the documentation they make use of PSFramework, but I decided to do it a little bit different.

# Create env variables
$Env:GEOCODING_TELEMETRY_OPTIN = (-not $Evn:POWERSHELL_TELEMETRY_OPTOUT) # use the invert of default powershell telemetry setting

# Set up the telemetry
Initialize-THTelemetry -ModuleName "Geocoding"
Set-THTelemetryConfiguration -ModuleName "Geocoding" -OptInVariableName "GEOCODING_TELEMETRY_OPTIN" -StripPersonallyIdentifiableInformation $true -Confirm:$false
Add-THAppInsightsConnectionString -ModuleName "Geocoding" -ConnectionString "<CONNECTION STRING>"

# Create a message about the telemetry
Write-Information ("Telemetry for Geocoding module is $(if([string] $Env:GEOCODING_TELEMETRY_OPTIN -in ("no","false","0")){"NOT "})enabled. Change the behavior by setting the value of "+ '$Env:GEOCODING_TELEMETRY_OPTIN') -InformationAction Continue

# Send a metric for the installation of the module
Send-THEvent -ModuleName "Geocoding" -EventName "Import Module Geocoding"

First of all a environment variable is defined. Since the start of PowerShell 7 it includes build in Telemetry you can optout too. I check if this value is set, if so you will also optout of my module.
Next up an initialization is done and settings are set. Lastly I include the connection string for a Application Insights in my Azure environment. This will ingest all the data.
I show a message on screen to notify users telemetry is being collected and after that I send a message to my Application Insights to record the module is imported. This message will automatically also include information about the users location so I can see in which countries the module is used.

In the main function there is a switch for the different providers, at the start of every case it shows a line like this:

Send-THEvent -ModuleName "Geocoding" -EventName "Find-GeoCodeLocation" -PropertiesHash @{Provider = "OSM" }

Here information about which provider is being used it being send to Applications Insights. This information is stored in the logs in Application Insights as a CustomDimension.

In the picture above you can see how this looks. To get an overview of which providers are used I created the following kusto query.

customEvents |
where  name == "Find-GeoCodeLocation" |
summarize Providers_Used = count() by tostring(parse_json(customDimensions).Provider)

In this query I only look for the events which are being triggered by using the function. And by using the parse_json() expression the value in the customDimension can be read and summarized by. To get an easy overview you can create a dashboard in Azure and pin this query to it.

Deploying the module to the PSGallery

To make sure everyone can use the module it’s useful to have this module available on the PSGallery. To be able to deploy modules to this repository you first need to create an account and get an API Key. Once you have this the PSModuleDevelopment makes things very easy again. If you make sure all files are uploaded to GitHub then it will automatically set up some Actions Workflows. By adding the API key as a secret in GitHub named APIKEY the Actions will automatically pick this up and use it.

There are two Actions which are being created:

  • Validate - this Workflow will check if the module is correct, it runs all kinds of tests including checking if help files are present. This Action will trigger on pull requests to the main branch.

  • Build - this Workflow will trigger once files are pushed to the main branch. It will validate the module and then deploy it to the PSGallery.

So after this you will see your module show up in the PSGallery.

Conclusion

With these tools it allowed me to produce a simple PowerShell module in only a couple of hours. So I can really recommend looking into these tools. You can find my module here on the PSGallery. At the moment it’s still in a 0.x.x phase which means it’s not yet production worthy. I hope some of my readers will take the time to test the module and any issues they find can be reported in the GitHub Repo. Hopefully nothing is found and I can soon make version 1.0.0.

Again if you are interested in PowerShell consider going to the PSConfEU there is a special action for women and non-binary people. If you are an organizer for PowerShell usergroups there is also the possibility for a discount. And if you can find a group of people from the same company to go with you can get a group discount. So I hope to see you all there and hopefully you will also join my session at the conference!