Keeping Azure PowerShell Current

I have been supporting Azure for 7 years now and one of the constants is the rapid pace of change of the services offered. This means that not only do you have to continually stay abreast of the most recent changes, but you also have to make sure that your tools stay updated, too. The Visual Studio team handles this by having the Visual Studio IDE check for updates on start up. Since PowerShell doesn't have an IDE per se, it isn't quite that simple. One of my Windows on Azure teammates, Adam Conkle, worked up a simple script that scraped the GitHub site and then pulled down the release package. It worked OK, but it's reliance on scraping the page and the method it used to pull down the installer weren't overly robust. I was playing around one day and ran across some code showing that you could call into WebPI programmatically, so decided to see if I could build something more robust.

First, I had to figure out how to track the releases. As it turned out, GitHub's general structure made this pretty easy. All I had to do was issue a GET against https://api.github.com/repos/Azure/azure-powershell/releases and then look at the name value in the JSON that was returned to get the version number:

 #GitHub URL for Azure PowerShell
 $url = "https://api.github.com/repos/Azure/azure-powershell/releases"
 
 #go get the current version on GitHub
 $results= (Invoke-RestMethod $url)
 $gitVer = $results[0].name
 $gitVerSimple=$gitVer.Replace(".","")

I should also note that PowerShell makes this really easy because it automatically converts the JSON result into an object with properties. If you really wanted to get fancy, you could also grab the "body" property to get the releases notes, but I decided not to do that in this case.

Once I had the version number from GitHub, the next step was to grab the version number from the locally installed version of PowerShell. Again, PowerShell makes this very easy:

 #get the local version
 $AzurePowerShell="C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Azure.psd1"
 
 #if the module isn't installed, we will end up with the default of 0
 if (Test-Path $AzurePowerShell)
 {
 Import-Module $AzurePowerShell
 $currVer = (Get-Module Azure).Version.ToString()
 Write-Host "Current version is $currVer"
 "Current version is $currVer" | Out-File -FilePath "$TARGETDIR\History.txt" -Append
 }
 else
 {
 Write-Host "Azure PowerShell not currently installed"
 "Azure PowerShell not currently installed" | Out-File -FilePath "$TARGETDIR\History.txt" -Append
 }

Doing some simple manipulation of the text strings, I could then do a simple comparison between the two versions. The next step at this point was taking advantage of WebPI to prevent me from having to figure out the installer file, manually pull it down, and then installing it. WebPI is nice in that you can search against it to find the particular products you want. In this case, I only wanted the Azure PowerShell cmdlets, so filtered my results using "WindowsAzurePowerShellOnly" "WindowsAzurePowershell" (changed in May 2015) "WindowsAzurePowerShellGet" (changed October 2015).

  #load up the assembly need to do the WebPI work
 [reflection.assembly]::LoadWithPartialName("Microsoft.Web.PlatformInstaller") | Out-Null
 $ProductManager = New-Object Microsoft.Web.PlatformInstaller.ProductManager
 $ProductManager.Load()
 
 #NOTE: Here's a handy way to visually see all the possibilities
 #$product = $ProductManager.Products | Where-Object { $_.ProductId -like "*PowerShell*" } | Select Title, ProductID | Out-GridView
 $product=$ProductManager.Products | Where { $_.ProductId -eq "WindowsAzurePowerShell" }
 $InstallManager = New-Object Microsoft.Web.PlatformInstaller.InstallManager
 $c = get-culture
 $Language = $ProductManager.GetLanguage($c.TwoLetterISOLanguageName)
 $installertouse = $product.GetInstaller($Language)
 $installer = New-Object 'System.Collections.Generic.List[Microsoft.Web.PlatformInstaller.Installer]'
 $installer.Add($installertouse)
 $InstallManager.Load($installer)

Each product has an installer that you can then pass to the WebPI installer engine and tell it to download and run the installer:

  #now that we have WebPI set up, go ahead and do the actual install
 $failureReason=$null
 foreach ($installerContext in $InstallManager.InstallerContexts) {
 $downloadresult=$InstallManager.DownloadInstallerFile($installerContext, [ref]$failureReason)
 Write-Host "Download result for $($installerContext.ProductName) : $downloadresult"
 "Download result for $($installerContext.ProductName) : $downloadresult" | Out-File -FilePath "$TARGETDIR\History.txt" -Append
 $InstallManager.StartSynchronousInstallation()
 if ($installerContext.ReturnCode.Status -eq "Success")
 {
 Write-Host "Upgrade complete. Log can be found at $($installerContext.LogFileDirectory)" -ForegroundColor Yellow
 "Upgrade Complete. Log can be found at $($installerContext.LogFileDirectory)" | Out-File -FilePath "$TARGETDIR\History.txt" -Append
 }
 else
 {
 Write-Host "Install failed with error $($installerContext.ReturnCode.Status). Log can be found at $($installerContext.LogFileDirectory)" -ForegroundColor Red
 "Install failed with error $($installerContext.ReturnCode) Log can be found at $($installerContext.LogFileDirectory)" | Out-File -FilePath "$TARGETDIR\History.txt" -Append
 }
 }

I did run into some trouble here at first in that the WebPI installer engine needs to be run as an administrator, so I did have to add some logic to the script to re-launch under an administrative instance of PowerShell if the initial shell wasn't run under that context. I should mention that when I first wrote this script, I defaulted to checking for administrative context when starting the script. However, I quickly got tired of having to acknowledge the request to run with administrative context every day. At that point, I moved the administrative context check to inside the version checking IF statement. This means that I only every have to interact with the script if there is actually an update to install.

Lastly, I added some basic logic to create a folder under MyDocuments to log the daily check, plus create a Scheduled Task to run the script every day. With all this in place, I can now rest assured that I will always be running the latest version of PowerShell. In fact, there are times now that my script detects the updated release on GitHub before I get the internal email notifying me about the new release!

To make it easy to share, I published the whole script at the Script Center (https://gallery.technet.microsoft.com/scriptcenter/Keep-Azure-PowerShell-478ab7ed). Feel free to borrow or improve on the script as you see fit.

Comments

  • Anonymous
    June 09, 2015
    hi evan, the script doesn't work. :-(  regards patrick PS C:windowssystem32> C:Demoauto Update Powershell Azure Moduleautoupdate powershell azure module.ps1 Script not found in C:UserspheydeDocumentsAzurePowerShellUpdater - copying over 06/10/2015 11:14:58 - Checking to see if Azure cmdlets need upgraded... Latest version on GitHub is 0.9.3 Current version is 0.8.10.1 Local version needs upgraded. Starting upgrade... Exception calling "Load" with "1" argument(s): "Object reference not set to an instance of an object." At C:Demoauto Update Powershell Azure Moduleautoupdate powershell azure module.ps1:119 char:9
  •         $InstallManager.Load($installer)
  •         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException    + FullyQualifiedErrorId : NullReferenceException
  • Anonymous
    June 24, 2015
    The comment has been removed

  • Anonymous
    August 30, 2015
    Would this not be a rather bad thing to do considering the upcoming ASM vs ARM changes?

  • Anonymous
    December 03, 2015
    Hey Mike, Not sure I follow. Why is it a bad thing? Evan