How to detect which version of the Az PowerShell module collection is installed on an Azure DevOps agent?

Viewed 4963

On Azure DevOps agents there is no Az module collection installed - Get-InstalledModule Az returns $null. But all Az modules are just available - Get-Module Az* -ListAvailable returns them all.

What is the best way to test if a particular version of the Az module collection is available? Unfortunately the Az module itself is not present in the regular module space; it only appears in the list of installed modules if installed: Get-Module Az -ListAvailable always returns $null.

Just to be sure we always test whether a particular minimum version of the Az module collection is installed. And if not, then we install it. As this easily takes a couple of minutes to complete, ideally we only do it when really necessary.

2 Answers

We have investigated Microsoft's Azure PowerShell task, as this task's feature is to enable an Az version of choice or choose the latest available. The latter is done by the function Get-LatestModule in Utility.ps1 which can be found here: https://github.com/microsoft/azure-pipelines-tasks/blob/master/Tasks/AzurePowerShellV5/Utility.ps1

Our full logic in the custom task is now capable of finding any installed version when running on a self-hosted agent or picking the latest on the Microsoft agent:

# On our self-hosted agent the Az module is installed
$installedVersion = (Get-InstalledModule -Name 'Az' -AllVersions -ErrorAction SilentlyContinue).Version | Sort-Object -Desc | Select-Object -First 1

if ('2.6.0' -gt $installedVersion) {
    # On Microsoft hosted agents the Az module itself is not present, but all the related Az modules are on disk in a specific folder
    # This code is taken from Microsoft Azure PowerShell task (https://github.com/microsoft/azure-pipelines-tasks/blob/master/Tasks/AzurePowerShellV5/Utility.ps1)
    $hostedAgentAzModulePath = Get-LatestModule -patternToMatch "^az_[0-9]+\.[0-9]+\.[0-9]+$" -patternToExtract "[0-9]+\.[0-9]+\.[0-9]+$"
    if (-not $hostedAgentAzModulePath) {
        # The hosted Az modules cannot be found. So proceed with installing it from the PowerShell gallery
        Write-Information -MessageData "INFO --- Install module 'Az'." -InformationAction Continue
        Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
        Install-Module -Name 'Az' -AllowClobber -Force -MinimumVersion '2.6.0' -Scope CurrentUser
    } else {
        # Append the Az modules path to the PowerShell modules path
        $env:PSModulePath = $hostedAgentAzModulePath + ";" + $env:PSModulePath
        $env:PSModulePath = $env:PSModulePath.TrimStart(';') 
    }
}

Import-Module -MinimumVersion '2.6.0' -Name 'Az' -Force -Scope 'Global'
Write-Information `
    -MessageData "INFO --- Imported Az module version $(Get-Module Az | Select-Object Version | ForEach-Object {$_.Version})." `
    -InformationAction Continue

Get-LatestModule copied from Utility.ps1:

function Get-LatestModule {
    [CmdletBinding()]
    param([string] $patternToMatch,
          [string] $patternToExtract)
    
    $resultFolder = ""
    $regexToMatch = New-Object -TypeName System.Text.RegularExpressions.Regex -ArgumentList $patternToMatch
    $regexToExtract = New-Object -TypeName System.Text.RegularExpressions.Regex -ArgumentList $patternToExtract
    $maxVersion = [version] "0.0.0"
    $modulePath = $env:SystemDrive + "\Modules";

    try {
        if (-not (Test-Path -Path $modulePath)) {
            return $resultFolder
        }

        $moduleFolders = Get-ChildItem -Directory -Path $modulePath | Where-Object { $regexToMatch.IsMatch($_.Name) }
        foreach ($moduleFolder in $moduleFolders) {
            $moduleVersion = [version] $($regexToExtract.Match($moduleFolder.Name).Groups[0].Value)
            if($moduleVersion -gt $maxVersion) {
                $modulePath = [System.IO.Path]::Combine($moduleFolder.FullName,"Az\$moduleVersion\Az.psm1")

                if(Test-Path -LiteralPath $modulePath -PathType Leaf) {
                    $maxVersion = $moduleVersion
                    $resultFolder = $moduleFolder.FullName
                } else {
                    Write-Verbose "A folder matching the module folder pattern was found at $($moduleFolder.FullName) but didn't contain a valid module file"
                }
            }
        }
    }
    catch {
        Write-Verbose "Attempting to find the Latest Module Folder failed with the error: $($_.Exception.Message)"
        $resultFolder = ""
    }
    Write-Verbose "Latest module folder detected: $resultFolder"
    return $resultFolder
}
Related