.github/scripts/Set-EnvironmentOnAgent.ps1

# Note: The installation commands in this script are optimized for Linux

#region Helper Functions
<#
.SYNOPSIS
Installes given PowerShell modules
 
.DESCRIPTION
Installes given PowerShell modules
 
.PARAMETER Module
Required. Modules to be installed, must be Object
@{
    Name = 'Name'
    Version = '1.0.0' # Optional
}
 
.PARAMETER InstalledModule
Optional. Modules that are already installed on the machine. Can be fetched via 'Get-Module -ListAvailable'
 
.EXAMPLE
Install-CustomModule @{ Name = 'Pester' } C:\Modules
 
Installes pester and saves it to C:\Modules
#>

function Install-CustomModule {

    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Parameter(Mandatory = $true)]
        [Hashtable] $Module,

        [Parameter(Mandatory = $false)]
        [object[]] $InstalledModule = @()
    )

    # Remove exsisting module in session
    if (Get-Module $Module -ErrorAction 'SilentlyContinue') {
        try {
            Remove-Module $Module -Force
        } catch {
            Write-Error ('Unable to remove module [{0}] because of exception [{1}]. Stack Trace: [{2}]' -f $Module.Name, $_.Exception, $_.ScriptStackTrace)
        }
    }

    # Install found module
    $moduleImportInputObject = @{
        name       = $Module.Name
        Repository = 'PSGallery'
    }
    if ($Module.Version) {
        $moduleImportInputObject['RequiredVersion'] = $Module.Version
    }

    # Get all modules that match a certain name. In case of e.g. 'Az' it returns several.
    $foundModules = Find-Module @moduleImportInputObject

    foreach ($foundModule in $foundModules) {

        # Check if already installed as required
        if ($alreadyInstalled = $InstalledModule | Where-Object { $_.Name -eq $Module.Name }) {
            if ($Module.Version) {
                $alreadyInstalled = $alreadyInstalled | Where-Object { $_.Version -eq $Module.Version }
            } else {
                # Get latest in case of multiple
                $alreadyInstalled = ($alreadyInstalled | Sort-Object -Property Version -Descending)[0]
            }
            Write-Verbose ('Module [{0}] already installed with version [{1}]' -f $alreadyInstalled.Name, $alreadyInstalled.Version) -Verbose
            continue
        }

        # Check if not to be excluded
        if ($Module.ExcludeModules -and $Module.excludeModules.contains($foundModule.Name)) {
            Write-Verbose ('Module {0} is configured to be ignored.' -f $foundModule.Name) -Verbose
            continue
        }

        Write-Verbose ('Install module [{0}] with version [{1}]' -f $foundModule.Name, $foundModule.Version) -Verbose
        if ($PSCmdlet.ShouldProcess('Module [{0}]' -f $foundModule.Name, 'Install')) {
            $foundModule | Install-Module -Force -SkipPublisherCheck -AllowClobber
            if ($installed = Get-Module -Name $foundModule.Name -ListAvailable) {
                Write-Verbose ('Module [{0}] is installed with version [{1}]' -f $installed.Name, $installed.Version) -Verbose
            } else {
                Write-Error ('Installation of module [{0}] failed' -f $foundModule.Name)
            }
        }
    }
}
#endregion

<#
.SYNOPSIS
Configure the current agent
 
.DESCRIPTION
Configure the current agent with e.g. the necessary PowerShell modules.
 
.PARAMETER PSModules
Optional. The PowerShell modules that should be installed on the agent.
 
@(
    @{ Name = 'Az.Accounts' },
    @{ Name = 'Az.Compute' },
    @{ Name = 'Az.Resources' },
    @{ Name = 'Az.ContainerRegistry' },
    @{ Name = 'Az.KeyVault' },
    @{ Name = 'Az.RecoveryServices' },
    @{ Name = 'Az.Monitor' },
    @{ Name = 'Az.CognitiveServices' },
    @{ Name = 'Az.OperationalInsights' },
    @{
        Name = 'Pester'
        Version = '5.3.1' # Version is optional
    }
)
 
.EXAMPLE
Set-EnvironmentOnAgent
 
Install the default PowerShell modules to configure the agent
#>

function Set-EnvironmentOnAgent {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [Hashtable[]] $PSModules = @()
    )

    ############################
    ## PowerShell version ##
    ############################

    Write-Verbose 'Powershell version:' -Verbose
    $PSVersionTable

    ###########################
    ## Install Azure CLI ##
    ###########################

    # AzCLI is pre-installed on GitHub hosted runners.
    # https://github.com/actions/virtual-environments#available-environments

    Write-Verbose 'Az CLI version:' -Verbose
    az --version
    <#
    Write-Verbose ("Install azure cli start") -Verbose
    curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
    Write-Verbose ("Install azure cli end") -Verbose
    #>


    ###############################
    ## Install Extensions CLI #
    ###############################

    # Azure CLI extension for DevOps is pre-installed on GitHub hosted runners.
    # https://github.com/actions/virtual-environments#available-environments

    Write-Verbose 'AZ CLI extensions:' -Verbose
    az extension list | ConvertFrom-Json | Select-Object -Property 'name', 'version', 'preview', 'experimental'

    <#
    Write-Verbose ('Install cli exentions start') -Verbose
    $Extensions = @(
        'azure-devops'
    )
    foreach ($extension in $Extensions) {
        if ((az extension list-available -o json | ConvertFrom-Json).Name -notcontains $extension) {
            Write-Verbose "Adding CLI extension '$extension'" -Verbose
            az extension add --name $extension
        }
    }
    Write-Verbose ('Install cli exentions end') -Verbose
    #>


    ####################################
    ## Install PowerShell Modules ##
    ####################################

    $count = 1
    Write-Verbose ('Try installing:') -Verbose
    $modules | ForEach-Object {
        Write-Verbose ('- {0}. [{1}]' -f $count, $_.Name) -Verbose
        $count++
    }

    # MS-hosted agents have pre-installed modules in a specific path. Let's make them discoverable if available.
    # Always create the $profile if it does not exist (to avoid later need of case handling)
    if (-not (Test-Path $profile)) {
        $null = New-Item -Path $profile -Force
    }
    if ((Test-Path '/usr/share/') -and ((Get-ChildItem -Path '/usr/share/az_*' -Directory).Count -gt 0)) {
        $preInstalledModulePaths = Get-ChildItem -Path '/usr/share/az_*' -Directory
        $maximumVersionPath = '/usr/share/az_{0}' -f (($preInstalledModulePaths | ForEach-Object { ($_ -split 'az_')[1] }) | ForEach-Object { [version]$_ } | Measure-Object -Maximum ).Maximum
        Write-Verbose "Found pre-installed modules in path [$maximumVersionPath]. Adding it PSModulePath environment variable." -Verbose

        if ($IsWindows) {
            # Set step module path (process)
            $env:PSModulePath += ";$maximumVersionPath"
            # Set job module path (machine)
            [Environment]::SetEnvironmentVariable('PSModulePath', ('{0};{1}' -f ([Environment]::GetEnvironmentVariable('PSModulePath', 'Machine')), $maximumVersionPath), 'Machine')
            # Set PS-Profile (for non-ps tasks)
            Add-Content -Path $profile -Value "`$env:PSModulePath += `";$maximumVersionPath`""
        } else {
            # Set step module path (process)
            $env:PSModulePath += ":$maximumVersionPath"
            # Set job module path (machine)
            [Environment]::SetEnvironmentVariable('PSModulePath', ('{0}:{1}' -f ([Environment]::GetEnvironmentVariable('PSModulePath', 'Machine')), $maximumVersionPath), 'Machine')
            # Set PS-Profile (for non-ps tasks)
            Add-Content -Path $profile -Value "`$env:PSModulePath += `":$maximumVersionPath`""
        }
    }

    # Load already installed modules
    $installedModules = Get-Module -ListAvailable

    Write-Verbose ('Install-CustomModule start') -Verbose
    $count = 1
    Foreach ($Module in $Modules) {
        Write-Verbose ('=====================') -Verbose
        Write-Verbose ('HANDLING MODULE [{0}/{1}] [{2}] ' -f $count, $Modules.Count, $Module.Name) -Verbose
        Write-Verbose ('=====================') -Verbose
        # Installing New Modules and Removing Old
        $null = Install-CustomModule -Module $Module -InstalledModule $installedModules
        $count++
    }

    Write-Verbose ('Install-CustomModule end') -Verbose
}