Azure Stack – Updating Microsoft Extensions via PowerShell

I recently came across an extremely useful PowerShell script to manage images and extensions within the Azure Stack marketplace thanks to this post by Kris Turner: https://kristopherjturner.com/2018/12/14/azure-stack-marketplace-management-powershell-script/

This script will download all current Microsoft virtual machine extensions from the marketplace. It also downloads Ubuntu Server and Windows Server images as well. Once all have been downloaded, it will compare versions and prompt you to remove older versions if desired.

The original version of this PowerShell script was written by John Savill and is available from his GitHub repository here: https://github.com/johnthebrit/AzureStack

After testing this script out myself on both the ASDK and an integrated system, I made the following modifications:

  • Prompt user to input ARM Admin endpoint URI
  • Prompt user to input AAD tenant name
    • This allows the Tenant ID to be obtained via REST API
  • Compare available extensions with currently installed extensions
    • This allows the user to see which extensions are missing and will be downloaded
  • Prompt user to download extensions
  • Only download and remove Microsoft extensions

The script requires PowerShell for Azure Stack to be installed which can be obtained here: https://docs.microsoft.com/en-us/azure-stack/operator/azure-stack-powershell-install

My modified version can be found on my GitHub here: https://github.com/mahoncj/AzureStack

### Original Author: John Savill - savilltech.com
### Contributing Author: Chris Mahon - blueflashylights.com

# Get ARM Admin Endpoint URI and AAD Tenant Name
Write-Host "Getting ARM Admin Endpoint" -ForegroundColor Green
$armEndpoint = Read-Host "Please enter your ARM Admin Endpoint URI (ex: https://adminmanagement.region.contoso.com)"
Write-Host ""
Write-Host "Getting AAD Tenant Name" -ForegroundColor Green
$AADTenantName = Read-Host "Please enter your AAD Tenant Name (ex: contoso.onmicrosoft.com)"
Write-Host ""

# Authenticate to the Azure Stack Environment with an account that has access to the Default Provider Subscription
Add-AzureRMEnvironment -Name "AzureStackAdmin" -ArmEndpoint $armEndpoint -ErrorAction Stop
$AuthEndpoint = (Get-AzureRmEnvironment -Name "AzureStackAdmin").ActiveDirectoryAuthority.TrimEnd('/')
$TenantId = (invoke-restmethod "$($AuthEndpoint)/$($AADTenantName)/.well-known/openid-configuration").issuer.TrimEnd('/').Split('/')[-1]
Add-AzureRmAccount -EnvironmentName "AzureStackAdmin" -TenantId $TenantId

# Verify Azure Stack is registered and activation resource group exists
$activationRG = "azurestack-activation"
$bridgeActivation = Get-AzsAzureBridgeActivation -ResourceGroupName $activationRG
$activationName = $bridgeActivation.Name

# Get available Microsoft extensions and compare with extensions already downloaded to Azure Stack marketplace
Write-Host "Comparing installed extensions with available extensions from the Marketplace"
$availableExtensions = ((Get-AzsAzureBridgeProduct -ActivationName $activationName -ResourceGroupName $activationRG -ErrorAction SilentlyContinue | Where-Object {($_.ProductKind -eq "virtualMachineExtension") -and ($_.Name -like "*microsoft*")}).Name) -replace "default/", ""
$myExtensions = ((Get-AzsAzureBridgeDownloadedProduct -ActivationName $activationName -ResourceGroupName $activationRG -ErrorAction SilentlyContinue | Where-Object {($_.ProductKind -eq "virtualMachineExtension") -and ($_.Name -like "*microsoft*")}).Name) -replace "default/", ""
$diffExtensions = Compare-Object $myExtensions $availableExtensions 
$missingExtensions = $diffExtensions | Where-Object {$_.SideIndicator -eq "=>"}
$missingExtensions = $missingExtensions.InputObject

if($missingExtensions)
    {
        # Print all missing Microsoft extensions in window
        Write-Host "The following Microsoft extensions have newer versions:"
        $missingExtensions | Write-Host -ForegroundColor Red
        Write-Host ""
        
        # Allow user to choose whether or not to download the missing Microsoft extensions
        $prompt = Read-Host "Do you want to download the missing extensions to the marketplace? (y/n)"
        Switch ($prompt)
        {
            Y
            {
                foreach ($missingExtension in $missingExtensions)
                {
                    Write-Host "Downloading $missingExtension to the Azure Stack Marketplace" -ForegroundColor Green
                    #Start-Sleep -Seconds 5
                    Invoke-AzsAzureBridgeProductDownload -ActivationName $activationName -Name $missingExtension -ResourceGroupName $activationRG -Force -Confirm:$false
                }
            }
            N
            {
                Write-Host "No extensions were downloaded!" -ForegroundColor Red
                Write-Host ""
            }
            Default
            {
                Write-Host "No extensions were downloaded!" -ForegroundColor Red
                Write-Host ""
            }
        }
    }
else
    {
        Write-Host "There are no missing extensions to download!" -ForegroundColor Green
        Write-Host ""
    }
        
#Get what is installed
Write-Host "Checking for older versions of Microsoft extensions"
$installedExtensions = Get-AzsAzureBridgeDownloadedProduct -ActivationName $activationName -ResourceGroupName $activationRG | Where-Object {($_.ProductKind -eq "virtualMachineExtension") -and ($_.Name -like "*microsoft*")}
$installedExtensions = $installedExtensions | Sort-Object -Property DisplayName, ProductProperties -Descending #want newest first as we'll look for matching and remove the second

$prevDisplayName = "Not going to match"
$prevEntry = $null
foreach($installed in $installedExtensions)
    {
        #see if name matches the previous, i.e. same extension
        if($installed.DisplayName -eq $prevDisplayName)
        {
            #Lets remove it 
            Write-Host "** Found an older version of $($installed.DisplayName) **"
            Write-Host "Previous version is $($installed.ProductProperties.Version) - $($installed.Name)" -ForegroundColor Red
            Write-Host "Current version is $($prevEntry.ProductProperties.Version) - $($prevEntry.Name)" -ForegroundColor Green
            Write-Host ""
            $Readhost = Read-Host "Do you want to delete previous version ($($installed.ProductProperties.Version)) (y/n)?"
            Switch ($ReadHost) 
            { 
                Y 
                {
                    Write-host "Yes, removing older extension version"; Remove-AzsAzureBridgeDownloadedProduct -Name $installed.Name -ActivationName $activationName -ResourceGroupName $activationRG -Force -Confirm:$false -ErrorAction Continue
                } 
                N 
                {
                    Write-Host "No, not removing older extension version"
                } 
                Default
                {
                    Write-Host "No, not removing older extension version"
                } 
            }
        
            Write-Host ""
        }
        $prevDisplayName = $installed.DisplayName
        $prevEntry = $installed
    }
Write-Host ""
Write-Host "Script complete!" -ForegroundColor Green

About the Author blueflashylights

Chris Mahon works as a Consultant Solutions Engineer at Dell EMC under the Cloud for Microsoft Azure Stack product group. As part of the engineering team, he assists with field enablement of resources for Support and Deploy Services (SDS), Consulting, and provides escalation support. In addition to that, the CSE team provides technical whitepapers and best practices pertaining to the Dell EMC Cloud for Microsoft Azure Stack product.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: