Building a provisioning script

This article describes how you'd create your own PowerShell-based provisioning script. In the given example, we'll provision a pre-configured virtual machine which can be used for e.g. coding interviews.

The script utilizes an ARM template that has been exported from Azure's Create a virtual machine wizard, where reasonable default values have been set up, e.g. which machine size and which OS to use. Once you've gone through the wizard, it lets you Download a template for automation. Extract those files into the folder where you intend to author your script. Speaking of ...

First we ensure that we have the az client tools installed, as we are dependent on them:

if ($null -eq (Get-Command az -ErrorAction Ignore)) {
    $installer = .\Download-File.ps1 `
        -DownloadUri 'https://aka.ms/installazurecliwindows' `
        -OutputFileName 'az-cli.msi'
    Start-Process "msiexec.exe" '/i',$installer
    Write-Host "-
    Please re-run this script IN A NEW TERMINAL
    after installation completes"
    exit
}

The Download File script is defined as:

[CmdletBinding()]
param (
    # Download URI
    [Parameter(Mandatory)]
    [string]
    $DownloadUri,

    # The desired file name (e.g. 'installer.msi')
    [Parameter(Mandatory)]
    [string]
    $OutputFileName,

    # Whether or not to download a new copy, even if the file already exists
    [switch]
    $ForceDownload
)    
$ResultingFileName = (Join-Path $env:TEMP $OutputFileName)
$ResultingFileExists = $null -ne (Get-Item $ResultingFileName -ErrorAction Ignore)
if ($ForceDownload -or -not $ResultingFileExists) {
    Invoke-WebRequest `
        -Uri $DownloadUri `
        -OutFile $ResultingFileName
}
return $ResultingFileName

To keep track of multiple actions as a single logical operation, we create a Correlation Id which we then include in all generated identifiers:

$CorrelationId = (New-Guid).ToString("n")
$DeploymentName = "interview-vm-$CorrelationId"
$ResourceGroupName = "rg-$DeploymentName"
$ProvisioningCleanUp = "CleanUp-$CorrelationId.ps1"

We also define some configuration settings which will be reused throughout our various provisioning commands:

$DesiredLocation = 'uksouth'
$Subscription = 'My fictional subscription name'


Alright! That's enough set-up. Let's actually start making things happen!

Log into Azure and fetch details about the subscription whose name we've configured:

$LoginInfo = ((az login) -join "" |
    ConvertFrom-Json |
    Where-Object -Property "name" -EQ $Subscription)

Set our context: We're intending to work with this specific subscription:

az account set --name "$Subscription"

Create a resource group to hold all of our resources:

$ResourceGroupInfo = (az group create `
    --name "$ResourceGroupName" `
    --location "$DesiredLocation") |
    ConvertFrom-Json
Add-Content $ProvisioningCleanUp "Write-Host '
    Cleaning up your resources.
    This will take approximately 5 minutes.'"
Add-Content $ProvisioningCleanUp "az`
    deployment group delete `
    --resource-group '$($ResourceGroupInfo.name)' `
    --name '$DeploymentName'"

There are a couple of things going on above. First, we're creating a resource group in our desired location, saving information about the operation as ResourceGroupInfo. Then we add information about de-provisioning into our cleanup script, followed about removal of a Deployment Group we haven't defined yet.

Let's fix that. As we've now provisioned a container for our virtual machine, let's provision the machine itself:

Write-Host "Provisioning your resources. This will take approximately 5 minutes."
Measure-Command {
$DeploymentInfo = (az deployment group create `
    --name "$DeploymentName" `
    --resource-group "$ResourceGroupName" `
    --template-file 'template.json' `
    --parameters '@parameters.json' `
    --parameters location="$DesiredLocation" `
    networkInterfaceName="networkInterface-$CorrelationId" `
    networkSecurityGroupName="networkSecurityGroup-$CorrelationId" `
    publicIpAddressName="publicIpAddress-$CorrelationId" `
    autoShutdownNotificationEmail="$InterviewerEmail")

    $DeploymentInfoFile = "Deployment info $CorrelationId.txt"
    Set-Content -Path $DeploymentInfoFile -Value $DeploymentInfo
    Add-Content $ProvisioningCleanUp "Remove-Item -Path '$DeploymentInfoFile' -Force"
}

As in the clean-up script, we're first setting some expectations for our user - this should take approximately 5 minutes. We've also wrapped our deployment into as an expression block of Measure-Command so that we - each time the script is run - get a new baseline, which we then can choose to update the expectation with.

Listing --parameters starting from the parameters.json file we downloaded from Azure lets us use the parameters file as a template, only overwriting a few select variables. We save information about the deployment in a text file and add a remove command for that text file in our clean-up script.

At this point (after approximately 5 minutes 😉), we should have a fully functional VM in the Cloud. 

Let's do some final bits of housekeeping and then make it super easy for our user to get a hold of this VM:

Add-Content $ProvisioningCleanUp
    "az group delete --name '$($ResourceGroupInfo.name)' --yes"
Add-Content $ProvisioningCleanUp
    "Remove-Item -Path '$ProvisioningCleanUp' -Force"

The above ensures that we remove the resource group (which in theory should remove everything else) and then remove the cleanup script itself, as it is no longer needed.

Finally, let's just open the Azure Portal to the connection pane of our new VM, so that we can easily download its .rdp file:

Start-Process @(
    'https://portal.azure.com/'
    $TenantName
    '/resource/subscriptions/'
    $($LoginInfo.id)
    '/resourceGroups/'
    $ResourceGroupName
    '/providers/Microsoft.Compute/virtualMachines/'
    $VirtualMachineName
    '/connect'
) -join ''

Using an array and then joining it into a full string lets us write easier-to-understand code that is logically segmented.

That's it! You're now ready to spin up virtual machines left and right! Just don't forget to run those clean up scripts 😉


Comments

Popular posts from this blog

Auto Mapper and Record Types - will they blend?

Unit testing your Azure functions - part 2: Queues and Blobs

Testing WCF services with user credentials and binary endpoints