Search for Help
< All Topics
Print

Introduction

All of the scripts that are used in PowerShell runbooks use some of the same functions, e.g. to connect to Azure, read SharePoint lists and send emails. Previously, copies of these functions were included in every runbook. If a bug was found in one of these, then the change had to be copied and pasted into each runbook in turn. A more established technique is to collate these functions into a PowerShell module that can then be included in each runbook – this significantly reduces the number of lines of PowerShell script in each runbook. The process of making updated copies of the PowerShell module available to the Azure runbook scripts can also be automated, by utilising an Azure DevOps deployment pipeline.

How its done

The following YouTube video describes the process of creating a PowerShell module, an associated git repository (for storing all the files) and creating a basic Azure DevOps deployment pipeline: Building a PowerShell module on Azure DevOps pipeline. This takes a user step-by-step through the process.

The article first describes how a PowerShell module is created using the ModuleBuilder format. Therefore MOduleBuilder needs to be installed using the following command, executed from PowerShell ISE or Visual Studio Code: Install-Module ModuleBuilder.

A basic example PowerShell module with a couple of example functions can be cloned using git, with the following command:

git clone https://github.com/thekamilpro/About-PowerShell.git

Nuget will also need to be installed. This can be downloaded from https://www.nuget.org/downloads and copy nuget.exe to C:\Program Files (x86)\NuGet and add C:\Program Files (x86)\NuGet

A new feed, PSModulesFeed was created. This was created by navigating to https://dev.azure.com/speakeasy-aphasia/SpeakeasyPowershellModule/, then clicking on the “Artifacts” link at the bottom of the left hand pane, then clicking “Create Feed”. The feed was made accessible to members of Speakeasy-aphasia only, with project scope.

Follow the video steps on publishing the nuget artifact to the feed. However, a user may encounter the following error when running the pipeline:

The nuget command failed with exit code(1) and error(Response status code does not indicate success: 403 (Forbidden – User ‘df8c07f8-8f9b-4661-a60c-f325f4e1f2d0’ lacks permission to complete this action. You need to have ‘AddPackage’. (DevOps Activity ID: D0D3CB14-9F39-41A4-8915-05FA59D19154)).)

To resolve this, the build service user needs Contributor access (by default it is given collaborator access only).

  • Click on “Artifacts” on the left side.
  • Select your feed from the drop down (usually selected by default)
  • Click the Feed Setting gear on the top right corner.
  • Click Permissions
  • Click Add Users/groups and search for {your org} Build Service and add as Contributor. You may need to delete the user from the list first – it doesn’t seem possible to amend a user’s permission level without first deleting the user.

The pipeline was modified with an additional step (over and above those specified in the video) so that the after PowerShell module has successfully deployed to PSGallery, the module is then automatically deployed to Azure Automation for use by all Speakeasy Azure Automation Runbooks. The follwoing section describes the setup needed for this deployment step to be successful.

Connecting to the Repository From a Local Computer

It is important to be able to connect to the repository from a computer (i.e. not via the cloud) so that the module can be installed and tested, then uninstalled again.
The article in the link given below was followed:
The process start with the creation of a PAT. (For our example a PAT called SpeakeasyReposPAT was created). Important: copy the token value that is generated immediately as it will not be visible once you navigate away from that screen.
Note: a PowerShell script that performs the process can be found in the SpeakeasyScripts repository, under:
SpeakeasyScripts\PowerShellScripts\ConnectToSpeakeasyPowershellModuleAzureDevopsRepo.ps1
A copy of this script can be made and the name of the PowerShell module changed (plus any PAT token) changed  as required.

Performing testing of a PowerShell module

Open the module’s constituent functions in VS Code, or use a separate PowerShell ISE session to build and test.

After each change/addition to any of the module’s functions, from a local PowerShell ISE (or VS Code) session, invoke Start-ModuleBuilder.ps1. This unloads any existing module, rebuilds the module and loads it into memory. Note, it use Import-module rather than Install-Module so re-run Start-ModuleBuilder.ps after each PowerShell ISE (or VSCode) session is restarted else the module’s functions won’t be found.

Commit changes back to git once happy – the Azure pipeline will run and create a new module package, deploy a nuspec package to the PSModulesFeed and deploy the module to PSGallery. The pipleine then deploys the PSGallery module to Azure automation for use by all Speakeasy Azure Automation runbooks. The module version and installation status (typically “Updating to Newer Version” or “Available”) can be seen in Azure under “SpeakeasyAutomation…Modules”

Installing a PowerShell module directly into SpeakeasyAutomation modules

By default, a local computer and Azure DevOps pipelines don’t have any access to Azure Automation, so instructions on how this is done are given in the two sections below. Only after an Azure Automation connection has successfully been established can any deployment of a PSGallery module be made to Azure Automation.

Configuration for a local computer

On the computer, try the following command: Add-AzureRmAccount

The command may fail with the following error:

Add-AzureRmAccount : Method ‘get_SerializationSettings’ in type ‘Microsoft.Azure.Management.Internal.Resources.ResourceManagementClient’ from assembly
‘Microsoft.Azure.Commands.ResourceManager.Common, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ does not have an implementation.
At line:1 char:1

If it does, try executing the following commands, them repeat the call to Add-AzureRmAccount, which should then work:

Remove-Module AzureRM.Profile -Force -ErrorAction SilentlyContinue # AzureRM causes a conflict with Az modules
Enable-AzureRmAlias -Scope CurrentUser -ErrorAction SilentlyContinue # solves type implementation exception

if (!(Get-Module -ListAvailable -Name Az.Accounts)) {
    Install-Module -Name Az.Accounts -Repository PSGallery -AllowClobber -Force -Scope CurrentUser
}
if (!(Get-Module -ListAvailable -Name Az.KeyVault)) {
    Install-Module -Name Az.KeyVault -Repository PSGallery -AllowClobber -Force -Scope CurrentUser
}

Import-Module Az.Accounts
Import-Module Az.KeyVault

Additional modules which are needed to enable deployment of the PowerShell module to Azure automation were added to the Install-SpeakeasyPowerShellModule\Install-Requirements.ps1 script . These included Az.Accounts and Az.Automation.

Configuration for an Azure DevOps Pipeline

The pipeline (defined in azure-pipelines.yml) used the AzurePowerShell@5 rather than the PowerShell@2 task. This was used so that a service connection name could be specified in the azureSubscription argunment of the task. This was set to “MyAzureServiceConnection”. This enables the Azure DevOps pipeline to connect to Azure automation without needing an interactive login. The connection can be seen at the following link:

https://dev.azure.com/speakeasy-aphasia/SpeakeasyPowershellModule/_settings/adminservices

The following article helped:
(https://learn.microsoft.com/en-gb/azure/devops/pipelines/library/connect-to-azure?view=azure-devops#create-an-azure-resource-manager-service-connection-using-workload-identity-federation)