Naar kennisoverzicht

Azure DevOps: Add a VM to a deployment group using the Azure CLI

The other day I was preparing a demo of Azure DevOps in which I use Deployment Groups to deploy an ASP.Net MVC web application to a Windows 2016 Server with IIS. Instead of creating this virtual machine through the portal I wanted to do things programmaticly to be able to easily repeat these steps so I can delete the vm or create more machines. There are two ways of doing this. Using ARM templates or using the Azure Cli. I don't like ARM templates too much. They tend to get very big, unreadable and are full of little details you need to know of to be able to work with them. So I figured I wanted to see what I could do using the Azure Cli.

Create the resources

There's a couple of things we need to create. Firts, we need a resource group. You can create one using the following command.

$ResourceGroup= "DeploymentGroupMachines"

$Location = "westeurope"

Write-Host "Create deployment group..."

az group create --name $ResourceGroup--location $Location

Next, we create the vm itself using the following command.

Write-Host "Create VM..."

$VmName = "W16DeployTest"

$VmUserName = "azureuser"

$VmPassword = "YourPassword01!"

az vm create --resource-group $ResourceGroup--name $VmName--image win2016datacenter --admin-username $VmUserName--admin-password $VmPassword

Since I want to deploy a website on this machine I need to open up port 80. By default only the remote desktop port is open.

Write-Host "Open port..."
az vm open-port --port 80 --resource-group $ResourceGroup --name $VmName

The Azure DevOps Agent extension

Last but not lease I need to install the Azure DevOps agent to use this machine as a deployment target. Luckily, there is a virtual machine extension which does just that. There was one problem however. I could not find any documentation on which parameters I needed to use as shown in the portal. There's this page (see Extensions Reference at the bottom of the menu) documenting some of the extensions. There's the following commands which give you information about the extensions.
Get-AzureRmVmImagePublisher -Location "WesternEurope" | `
Get-AzureRmVMExtensionImageType | `
Get-AzureRmVMExtensionImage | Select Type, Version

Non of them however showed me how to use this particular one. After searching the internet for a while I found this answer on GitHub. Although it's not a direct answer to my problem, it did give enough hints. The following snippet shows the parameters I have to use.

"settings":
{
   "VstsAccountName": "[parameters('vstsAccount')]",
   "TeamProject": "[parameters('TeamProject')]",
   "DeploymentGroup": "[parameters('DeploymentGroup')]",
   "AgentName": "[parameters('vmName')]",
   "Tags": "[parameters('Tags')]"
},
"protectedSettings": {
   "PATToken": "[parameters('personalAccessToken')]"
}

They lead to the following command:

Write-Host "Add extension..."

$VSTSAccountName = "<AccountName>"

$TeamProject = "<TeamProject>"

$DeploymentGroup = "Test"

$Token= "<TOKEN>"

az vm extension set --name TeamServicesAgent --publisher Microsoft.VisualStudio.Services --version 1.23.0.0--vm-name $VmName--resource-group $ResourceGroup--settings "{\""VSTSAccountName\"": \""$VSTSAccountName\"",\""TeamProject\"": \""$TeamProject\"",\""DeploymentGroup\"": \""$DeploymentGroup\""}"--protected-settings "{\""PATToken\"": \""$Token\""}"

After you’ve run these commands you’ll see this machine pop up in your deployment group ready to use.

Machine configuration

I use the following two scripts to make my machine ready for my application to be deployed. I run them on the machine in a release pipeline. The first one installs same required dependencies. The second one applies a PowerShell DSC configuration to install IIS and some .Net frameworks. You might wonder why I install .Net 3.5. This is a requirement of the Website Deployment Task in Azure DevOps.

Install-PackageProvider NuGet -Force
Import-PackageProvider NuGet -Force
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted

Install-Module -Name xWebAdministration

$env:PSModulePath = [System.Environment]::GetEnvironmentVariable("PSModulePath","Machine")
Configuration ServerConfig 
{ 

    Import-DscResource -Module xWebAdministration
    Node "localhost" {   

        WindowsFeature installIIS 
        { 
            Ensure="Present" 
            Name="Web-Server" 
        }

        WindowsFeature installIISManagementConsole
        { 
            Ensure="Present" 
            Name="Web-Mgmt-Console" 
        }

        WindowsFeature installWebAspNet45
        { 
            Ensure="Present" 
            Name="Web-Asp-Net45" 
        }

WindowsFeature installNetFramework35
        { 
            Ensure="Present" 
            Name="Net-Framework-Core" 
        }
        
        # Stop the default website 
         xWebsite DefaultSite  
         { 
             Ensure          = 'Present' 
             Name            = 'Default Web Site' 
             State           = 'Stopped' 
             PhysicalPath    = 'C:\inetpub\wwwroot' 
             DependsOn       = '[WindowsFeature]installIIS'
 BindingInfo     = @( MSFT_xWebBindingInformation
                                 {
                                   Protocol              = "HTTP"
                                   Port                  = 81
                                 }
                             )
        }
    }    
}

ServerConfig -OutputPath "C:\DscConfiguration"

Start-DscConfiguration -Wait -Verbose -Path "C:\DscConfiguration"