Overview
This post is a continuation of my previous post on Updating a Cloud Service (Extended Support) and Powershell Script to create a new Cloud Services (Extended Support) Deployment. In the previous posts, I covered all the steps necessary to manually update a Cloud Services resource from the Azure Portal as well as create a new cloud service using PowerShell. In this post, we will cover updating a cloud Service, but with PowerShell scripts that use the Azure Az PowerShell module.
As before, you will need to ensure that you have installed Powershell 7+ and the Az PowerShell Module before the script can be run. Restart all command prompt windows after you have installed these dependencies. Make sure to use the PowerShell 7 terminal and not the Windows PowerShell or Command Prompt terminal windows.
You will also have to set your PowerShell session to trust running scripts locally by setting the Execution Policy to RemoteSigned.
Set-ExecutionPolicy RemoteSigned
As usual, all the code can be found on my GitHub profile.
Preliminary setup
In the new ARM deployment model, each deployment is its own complete resource, so even though one Cloud Service resource can be swopped with another, it can also run completely independently. We need to set up a Public IP address that will be assigned to the second cloud service being deployed. Eventually, this IP address will be swapped with the one currently pointing to the Cloud Service running in Azure, but to do the deployment, it requires its own IP address.
Run the following in PowerShell to set this up. It only must be done once so do not add it to the script file..
# Set the location
$location = "southafricanorth";
# Sign In To Azure
Connect-AzAccount
# Set Context
Set-AzContext -Tenant "<YOUR TENANT ID>"
# Create the “staging” IP Address.
$ipAddressStaging = New-AzPublicIpAddress -ResourceGroupName "MzansiBytes" -Name "MzansiBytes-Staging" -AllocationMethod Static -DomainNameLabel "mzansibytes-staging" -Location $location -Sku "Basic"
You’ll notice this is like what we used in the script to create the Cloud Service resource. The main differences are that we put the Resource Group name in directly instead of referring to a Resource Group variable and adding -Staging to the end of the -Name and -DomainNameLabel arguments.
Next, update the ServiceConfiguration.Cloud.cscfg file to use the new staging IP address.
<ReservedIPs>
<ReservedIP name="mzansibytes-staging" />
</ReservedIPs>
This will make the new deployment use this staging IP address initially. Then when we perform a swap on Azure, the staging IP address will be replaced with the production IP address, and the currently running instance will get the staging IP address.
ARM Templates
Update deployments require using ARM templates so that a swappable Cloud Service can be specified. Create the following two files:
You can click the links to view their contents in GitHub.
Note: You will need to make a change under the osProfile section. You need to set your Subscription ID, Resource Group Name, Key Vault Name and Certificate ID. You can find your Certificate ID by going to the Key Vault in Azure, and then navigating to Certificates, clicking on the certificate, clicking on the Current version, and then copying the Certificate Identifier.
The script
Firstly, create a new PowerShell file called UpdateCloudService.ps1 in the same folder as the solution file. All the PowerShell code will be added to this file. I also recommend using Visual Studio Code with the Powershell extension installed while developing this – it makes things much easier.
# Set the location
$location = "southafricanorth";
Next, the script needs to sign into Azure and set the correct tenant to use. You can find your tenant by following these instructions. When running the script, you will need to sign into your Azure account for the script to continue.
# Sign In To Azure
Connect-AzAccount
# Set Context
Set-AzContext -Tenant "YOUR TENANT ID"
The script needs to call MSBuild.exe to package the project. Change the MSBuild.exe location if you have a different version of Visual Studio installed, or if it is not installed in the default location.
# Package the project
$msBuildFilePath = "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe"
.$msBuildFilePath "$PSScriptRoot\MzansiBytes\MzansiBytes.ccproj" `
/p:Configuration=Release `
/p:PublishDir="$PSScriptRoot\Temp\CloudPackage" `
/p:TargetProfile="Cloud" `
/p:Platform=AnyCpu `
/t:Publish
Next, the Cloud Service needs to be deployed on Azure. This requires uploading the package files to Azure Storage, and finding out which Cloud Service is currently running, so that we can deploy this one with a different name.
# Upload configuration and package to Azure and get SAS URIs"
$storageAccount = Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name "mzansibytes"
$configurationBlob = Set-AzStorageBlobContent -Context $storageAccount.Context -Force -Container "temp" -File "$PSScriptRoot\Temp\CloudPackage\ServiceConfiguration.Cloud.cscfg" -Blob "CloudPackage\ServiceConfiguration.cscfg"
$configurationBlobSasToken = New-AzStorageBlobSASToken -Context $storageAccount.Context -FullUri -Container "temp" -Blob "$($configurationBlob.Name)" -Permission rwd
$packageBlob = Set-AzStorageBlobContent -Context $storageAccount.Context -Force -Container "temp" -File "$PSScriptRoot\Temp\CloudPackage\MzansiBytes.cspkg" -Blob "CloudPackage\MzansiBytes.cspkg"
$packageBlobSasToken = New-AzStorageBlobSASToken -Context $storageAccount.Context -FullUri -Container "temp" -Blob "$($packageBlob.Name)" -Permission rwd
# Get the currently running cloud service.
if (Get-AzCloudService -ResourceGroupName $resourceGroupName -CloudServiceName "MzansiBytes-1" -ErrorAction SilentlyContinue) # -ErrorAction SilentlyContinue is used so that if this is false, PowerShell doesn't print a bunch of Azure errors to the screen.
{
$currentCloudService = Get-AzCloudService -ResourceGroupName $resourceGroupName -CloudServiceName "MzansiBytes-1"
$cloudServiceToDeploy = "MzansiBytes-2"
}
elseif (Get-AzCloudService -ResourceGroupName $resourceGroupName -CloudServiceName "MzansiBytes-2" -ErrorAction SilentlyContinue)
{
$currentCloudService = Get-AzCloudService -ResourceGroupName $resourceGroupName -CloudServiceName "MzansiBytes-2"
$cloudServiceToDeploy = "MzansiBytes-1"
}
else {
throw "No cloud service was found."
}
Next, we need to set various properties in the ARM template parameters file so that it matches up with what is set in the .cscfg file. I don’t know why Azure can’t just read the .cscfg file, but instead requires us to specify the same thing in two separate places.
# Set the ARM template parameters
$parametersJson = Get-Content "$PSScriptRoot\Parameters.json" | ConvertFrom-Json
$parametersJson.parameters.cloudServiceName.value = $cloudServiceToDeploy
$parametersJson.parameters.configurationSasUri.value = $configurationBlobSasToken
$parametersJson.parameters.packageSasUri.value = $packageBlobSasToken
$parametersJson.parameters.publicIPName.value = "mzansibytes-staging"
$parametersJson.parameters.location.value = $location
$parametersJson.parameters.startCloudService.value = $true
$parametersJson.parameters.swappableCloudService.value = "$($currentCloudService.Id)"
$parametersJson.parameters.vnetName.value = $virtualNetworkName
$parametersJson.parameters.vnetId.value = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Network/virtualNetworks/$virtualNetworkName"
$parametersJson | ConvertTo-Json | Set-Content "$PSScriptRoot\Parameters.json"
Finally, we can create the Cloud Service resource.
# Create the Cloud Service Resource
New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile "$PSScriptRoot\Template.json" -TemplateParameterFile "$PSScriptRoot\Parameters.json"
Once the resource is created, we can swap in with the other Cloud Service.
# Swap VIPs
Switch-AzCloudService -ResourceGroupName $resourceGroupName -CloudServiceName $currentCloudService.Name -Confirm:$false
Now the only thing left to do is to clean up after ourselves by deleting the temp folder and removing the “old” Cloud Service.
# Cleanup
Remove-Item "$PSScriptRoot\Temp" -Recurse -Force
Remove-AzCloudService -ResourceGroupName $resourceGroupName -CloudServiceName $currentCloudService.Name
We’re done!