Runbook to update Az Module on Octopus server does not work

I have created a runbook on my Octopus server that is intended to update the version of the PowerShell Az module installed on the server. The script is very simple:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

Write-Host "Current version:"
Get-InstalledModule -Name Az -AllVersions | select Name,Version

Write-Host "Running update..."
Update-Module -Name Az

Write-Host "Updated version:"
Get-InstalledModule -Name Az -AllVersions | select Name,Version

When I run it, I get the following error:

Current version: 

July 23rd 2021 16:17:24Error
ObjectNotFound: No match was found for the specified search criteria and module names ‘Az’.
July 23rd 2021 16:17:24Error
At C:\program files\powershell\7\Modules\PowerShellGet\PSModule.psm1:9445 char:9
July 23rd 2021 16:17:24Error

  •     PackageManagement\Get-Package @PSBoundParameters | Microsoft. . 
    

July 23rd 2021 16:17:24Error

  •     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    

July 23rd 2021 16:17:24Error
at Get-InstalledModule, C:\program files\powershell\7\Modules\PowerShellGet\PSModule.psm1: line 9445
July 23rd 2021 16:17:24Error
at , C:\Octopus\Work\20210723211647-82642-60\Script.ps1: line 4
July 23rd 2021 16:17:24Error
at , C:\Octopus\Work\20210723211647-82642-60\Bootstrap.Script.ps1: line 1654
July 23rd 2021 16:17:24Error
at , : line 1
July 23rd 2021 16:17:24Error
at , : line 1
July 23rd 2021 16:17:25Fatal
The remote script failed with exit code 1
July 23rd 2021 16:17:25Fatal
The action Update Az Module on the Octopus Server failed

I have the runbook configured to run locally on the Octopus server and to use PowerShell Core. This script runs fine locally in a PowerShell console on the server itself.

What is causing the issue? Is this possible?

Thanks!

Hi David,

Yes, that is completely possible. In fact, a lot of our step templates will install PS modules for you.

In looking at the code for some of them I see these functions:


$ModulesFolder = "$Home\Documents\WindowsPowerShell\Modules"
Write-Host "Modules will be installed into $ModulesFolder"

$LocalModules = (New-Item "$ModulesFolder" -ItemType Directory -Force).FullName
Write-Host "LocalModules: $LocalModules"
$env:PSModulePath = "$LocalModules;$env:PSModulePath"
Write-Host $env:PSModulePath

function GetHighestInstalledModule {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $moduleName,
    )    

    return Get-Module $moduleName -ListAvailable |  
           Sort -Property @{Expression = {[System.Version]($_.Version)}; Descending = $True} |
           Select -First 1
}

function GetHighestInstallableModule {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $moduleName
    )

    try {
        InstallPowerShellGet
        Find-Module $moduleName -AllVersions | 
            Sort -Property @{Expression = {[System.Version]($_.Version)}; Descending = $True} |
            Select -First 1    
    }
    catch {
        Write-Warning "Could not find any suitable versions of $moduleName from any registered PSRepository"
    }
}

function InstallPowerShellGet {
    [CmdletBinding()]
    Param()
    $psget = GetHighestInstalledModule PowerShellGet
    if (!$psget)
    {
        Write-Warning @"
Cannot access the PowerShell Gallery because PowerShellGet is not installed.
To install PowerShellGet, either upgrade to PowerShell 5 or install the PackageManagement MSI.
See https://docs.microsoft.com/en-us/powershell/gallery/installing-psget for more details.
"@
        throw "PowerShellGet is not available"
    }

    if ($psget.Version -lt [Version]'1.6') {
        #Bootstrap the NuGet package provider, which updates NuGet without requiring admin rights
        Write-Debug "Installing NuGet package provider"
        Get-PackageProvider NuGet -ForceBootstrap | Out-Null

        #Use the currently-installed version of PowerShellGet
        Import-PackageProvider PowerShellGet 
        
        #Download the version of PowerShellGet that we actually need
        Write-Debug "Installing PowershellGet"
        Save-Module -Name PowerShellGet -Path $LocalModules -MinimumVersion 1.6 -Force -ErrorAction SilentlyContinue
    }

    Write-Debug "Importing PowershellGet"
    Import-Module PowerShellGet -MinimumVersion 1.6 -Force
    #Make sure we're actually using the package provider from the imported version of PowerShellGet
    Import-PackageProvider ((Get-Module PowerShellGet).Path) | Out-Null
}

function InstallLocalModule {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$moduleName,
        [Parameter(Mandatory = $true)]
        [string]$localModules
    )
    try {
        InstallPowerShellGet

        Write-Debug "Install $moduleName $requiredVersion"
        Save-Module -Name $moduleName -Path $LocalModules -Force -AcceptLicense -ErrorAction Stop
    }
    catch {
        Write-Warning "Could not install $moduleName $requiredVersion from any registered PSRepository"
    }
}

Putting that together:

$installedModule = GetHighestInstalledModule $moduleName

    if (!$installedModule) {
        Write-Verbose "$moduleName not available - attempting to download from gallery"
        InstallLocalModule -moduleName $moduleName 
    }
    else {
        $newest = GetHighestInstallableModule $moduleName
        if ($newest -and ($installedModule.Version -lt $newest.Version)) {
            Write-Verbose "Updating $moduleName to version $($newest.Version)"
            InstallLocalModule -moduleName $moduleName
        }
    }

I hope those examples help!