18 Steps for End-to-End IaaS Provisioning in the Cloud with Azure Resource Manager (ARM), PowerShell and Desired State Configuration (DSC)

Support for provisioning and managing Azure IaaS virtual machine environments via Azure Resource Manager (ARM) was recently launched as a Generally Available (GA) feature! There’s several HUGE advantages that ARM provides over the existing Azure Service Management (ASM) API, including simplifying complex configurations, repeatable deployments via declarative templates, resource tagging, role-based access control (RBAC) and more!  You can learn more about the advantages of this new API at:

With the GA launch of ARM for Virtual Machine services, lots of us are exploring how to leverage this new API as part of an automated provisioning process.  So, I thought I’d update my previous guidance on automated provisioning of IaaS environments for using the new Azure Resource Manager.

In this article, we’ll step through using the latest Azure PowerShell module, which is version 0.9.4 as of this article’s publication date, to perform end-to-end provisioning of a common IaaS VM scenario: load-balanced web applications.  My goal in this article is to provide you with code snippets that can serve as “building blocks” for learning, demo'ing and beginning to create your own automated provisioning process using Azure Resource Manager, PowerShell and Desired State Configuration. To promote readability, I haven't included error handling in these snippets, so I'll leave that for you to incorporate with respect to the scenario that you're building.

Azure IaaS Scenario

In future articles, we’ll look at provisioning this same scenario using other tools and approaches, such as Azure Resource Manager Templates and also the Azure Cross-Platform CLI for Linux and Mac OSX.

1. Activate your Azure Subscription

To work through the steps in this article, you’ll need an active Azure subscription.  If you don’t currently have an active Azure subscription, you can sign-up for a Free Trial offer.  Alternatively, MSDN subscribers can activate their free Azure subscription benefits.

2. Install the Latest Azure PowerShell Modules

Before proceeding with the code snippets in this article, be sure to install and configure the latest version of the Azure PowerShell modules (v0.9.4 or later) so that you’re working with the latest cmdlets to support Azure Resource Manager.

3. Authenticate to your Azure Account

After installing Azure PowerShell, open the Windows PowerShell ISE console, and run the Add-AzureAccount cmdlet to authenticate to Azure with the same credentials you used in Step 1 to activate your Azure subscription.

# Authenticate to Azure Account


If you are authenticating with Azure Active Directory credentials, you can also use the -Credential parameter to pass your credentials to the Add-AzureAccount cmdlet in an automated fashion.

# Authenticate with Azure AD credentials

$cred = Get-Credential

Add-AzureAccount `
    -Credential $cred

4. Switch to Azure Resource Manager Mode

In the current version of Azure PowerShell, which is v0.9.4 as of this article’s publication date, you must switch to a special “mode” to be able to run the cmdlets for calling Azure Resource Manager.   You can perform this task using the Switch-AzureMode cmdlet.

# Switch to Azure Resource Manager mode

Switch-AzureMode `
    -Name AzureResourceManager

Note that this cmdlet has been deprecated and will be removed in future versions of Azure PowerShell.  Be sure to read this thread to understand how Azure Resource Manager cmdlets will be accessible in future versions of Azure PowerShell.

5. Register the latest Azure Resource Manager Providers

When using ARM in your Azure subscription for the first time, you may find that you need to register the latest version of the ARM Providers for Compute, Storage and Network resources.

# Register the latest ARM Providers

Register-AzureProvider `
    -ProviderNamespace Microsoft.Compute

Register-AzureProvider `
    -ProviderNamespace Microsoft.Storage

Register-AzureProvider `
    -ProviderNamespace Microsoft.Network

After registering these providers, you can confirm successful registration by running the Get-AzureProvider cmdlet.

# Confirm registered ARM Providers

Get-AzureProvider |
     Select-Object `
        -Property ProviderNamespace `
        -ExpandProperty ResourceTypes

6. Select Azure Subscription

Next, you’ll need to select the Azure subscription in which you’ll be provisioning new cloud resources.  Use the code snippet below to display a list of your Azure subscriptions and select the appropriate subscription to use for the remaining steps in this article.

# Select an Azure subscription

$subscriptionId =
    (Get-AzureSubscription |
     Out-GridView `
        -Title "Select a Subscription …" `

Select-AzureSubscription `
    -SubscriptionId $subscriptionId

7. Select Azure Datacenter Region

Use this snippet to select the Azure Datacenter Region in which you’ll be provisioning new resources.

# Select an Azure Datacenter Region

$location = `
    ( Get-AzureLocation | `
        ? Name -eq "ResourceGroup" ).Locations | `
    Out-GridView `
        -Title "Select a Region …" `

After selecting an Azure Datacenter Region, you can confirm the ARM Providers and Resource Types available in that region by running the Get-AzureLocation cmdlet.

# Show available Providers & Resource Types

( Get-AzureLocation | `
    ? Locations -Contains $location ).Name

8. Create Azure Resource Group

ARM provides the ability to organize related resources in a Resource Group as a logical grouping that can be used to manage these resources in a related way.  Using Role-Based Access Control (RBAC), granular administrative access can be delegated to entire resource groups or individual resources within a resource group.  In addition, custom Tags can be assigned to resource groups and individual resources to filter, categorize and view collections of resources across resource groups based on common tag values.

# Define a unique prefix for naming resources
# in this deployment

$prefix = "XXXarmdemo" # replace XXX with a unique lowercase value

# Define custom Tags to be used for new deployment

$tags = New-Object System.Collections.ArrayList
$tags.Add( @{ Name = "project"; Value = "demo" } )
$tags.Add( @{ Name = "costCenter"; Value = "0001" } )

# Define number of VMs to provision in Resource Group

$vmInstances = 2

# Create Resource Group if it doesn't already exist

$rgName = "${prefix}-rg"

If (!(Test-AzureResourceGroup `
    -ResourceGroupName $rgName)) {

    $rg = New-AzureResourceGroup `
        -Name $rgName `
        -Location $location `
        -Tag $tags

} Else {

    # Get Resource Group if already created

    $rg = Get-AzureResourceGroup `
        -Name $rgName


In the above code snippet, we’re also defining a few variables that we’ll reuse across many of the remaining steps in this article:

  • $prefix – to provide a unique naming convention for resources being provisioned as part of this deployment
  • $tags – a set of consistent custom tag values, by which we can filter, categorize and view resources in the future
  • $vmInstances – total number of VMs to be provisioned, which we’ll use as an index value for any unique configuration objects per each VM

9. Create Azure Storage Account

Much like when provisioning VMs via the existing Azure Service Management API, when provisioning VMs via ARM, we’ll still need a Storage Account in which to store VHDs for the OS disks and data disks for each VM.  Let’s create our Storage Account next …

# Define a unique name for the new Storage Account

$storageAccountName = "${prefix}stor01"

# Select the type of Storage Account to create

$storageAccountType =
    (Get-Command `
        -Name New-AzureStorageAccount).
            ValidValues |
    Out-GridView `
        -Title "Select Storage Account Type" `

# Create Storage Account if it doesn't exist

if (!(Test-AzureResource `
    -ResourceName $storageAccountName `
    -ResourceType "Microsoft.Storage/storageAccounts" `
    -ResourceGroupName $rgName)) {

    $storageAccount = New-AzureStorageAccount `
        -Name $storageAccountName `
        -ResourceGroupName $rgName `
        -Location $location `
        -Type $storageAccountType

} else {

    # Get Storage Account if already created

    $storageAccount = Get-AzureStorageAccount `
        -ResourceGroupName $rgname `
        -Name $storageAccountName


# Set new Storage Account as default

Set-AzureSubscription `
    -SubscriptionId $subscriptionId `
    -CurrentStorageAccountName $storageAccountName

10. Create Azure Virtual Network

Next, we’ll define the Azure Virtual Network to provide the VMs with an internal private IP address space via which they can communicate with one another.

# Define a unique name for the Azure VNET

$vnetName = "${prefix}-vnet"

# Define names for each Subnet within the VNET

$subnet1Name = "${prefix}-subnet01"

$subnet2Name = "GatewaySubnet"

# Create Virtual Network if it doesn't exist

if (!(Test-AzureResource `
    -ResourceName $vnetName `
    -ResourceType "Microsoft.Network/virtualNetworks" `
    -ResourceGroupName $rgName)) {

    $subnet1 = New-AzureVirtualNetworkSubnetConfig `
        -Name $subnet1Name `
        -AddressPrefix ""

    $subnet2 = New-AzureVirtualNetworkSubnetConfig `
        -Name $subnet2Name `
        -AddressPrefix ""

    $vnet = New-AzureVirtualNetwork `
        -Name $vnetName `
        -ResourceGroupName $rgName `
        -Location $location `
        -AddressPrefix "" `
        -Subnet $subnet1, $subnet2 `
        -Tag $tags

} else {

    # Get Virtual Network if already created

    $vnet = Get-AzureVirtualNetwork `
        -Name $vnetName `
        -ResourceGroupName $rgName


11. Define a Public VIP Address

Unlike the existing Azure Service Management provisioning model, Azure VMs do not automatically get created inside a Cloud Service that provides a Public VIP Address.  Instead, when using Azure Resource Manager, we’ll need to define a Public VIP Address as a separate resource object, if our VMs need to be reachable from the Internet.

# Define a unique name for the VIP resource

$publicVipName = "${prefix}-vip"

# Define a DNS hostname to be assigned to the VIP

$domainName = "${prefix}app"

# Define Public VIP Address, if not created

if (!(Test-AzureResource `
    -ResourceName $publicVipName `
    -ResourceType "Microsoft.Network/publicIPAddresses" `
    -ResourceGroupName $rgName)) {

    $publicVip = New-AzurePublicIpAddress `
        -Name $publicVipName `
        -ResourceGroupName $rgName `
        -Location $location `
        -AllocationMethod Dynamic `
        -DomainNameLabel $domainName `
        -Tag $tags

} else {

    # Get Public VIP Address if already created

    $publicVip = Get-AzurePublicIpAddress `
        -Name $publicVipName `
        -ResourceGroupName $rgName


12. Create a Load Balancer

Similarly, Load Balancers are also exposed as separate resource objects when using Azure Resource Manager.  Let’s define a Load Balancer next that will use the Public VIP Address created in Step 11 to load-balance HTTP traffic amongst a set of VMs to be provisioned later in this article.  In addition, we’ll also define a set of inbound NAT rules that will permit us to establish a Remote Desktop connection to each individual VM via the Public VIP Address.

# Define a unique name for Load Balancer resource

$lbName = "${prefix}-lb"

# Create the Load Balancer, if not yet created

if (!(Test-AzureResource `
    -ResourceName $lbName `
    -ResourceType "Microsoft.Network/loadBalancers" `
    -ResourceGroupName $rgName)) {

    # Front-end IP Config using Public VIP

    $lbFeIpConfigName = "lb-feip"

    $lbFeIpConfig = New-AzureLoadBalancerFrontendIpConfig `
        -Name $lbFeIpConfigName `
        -PublicIpAddress $publicVIP

    # Inbound NAT Rules for Remote Desktop per VM

    $lbInboundNatRules = @()

    for ($count = 1; $count -le $vmInstances; $count++) {

        $ruleName = "nat-rdp-${count}"

        $frontEndPort = 3389 + $count

        $backEndPort = 3389

        $lbInboundNatRules += `
            New-AzureLoadBalancerInboundNatRuleConfig `
                -Name $ruleName `
                -FrontendIpConfigurationId $lbFeIpConfig.Id `
                -Protocol Tcp `
                -FrontendPort $frontEndPort `
                -BackendPort $backEndPort


    # Back-end IP Address Pool

    $lbBeIpPoolName = "lb-be-ip-pool"

    $lbBeIpPool = `
        New-AzureLoadBalancerBackendAddressPoolConfig `
            -Name $lbBeIpPoolName

    # Health Check Probe Config for HTTP

    $lbProbeName = "lb-probe"

    $lbProbe = New-AzureLoadBalancerProbeConfig `
        -Name $lbProbeName `
        -RequestPath "/" `
        -Protocol Http `
        -Port 80 `
        -IntervalInSeconds 15 `
        -ProbeCount 2

   # Load Balancing Rule for HTTP

    $lbRuleName = "lb-http"

    $lbRule = New-AzureLoadBalancerRuleConfig `
        -Name $lbRuleName `
        -FrontendIpConfigurationId $lbFeIpConfig.Id `
        -BackendAddressPoolId $lbBeIpPool.Id `
        -ProbeId $lbProbe.Id `
        -Protocol Tcp `
        -FrontendPort 80 `
        -BackendPort 80 `
        -LoadDistribution Default

    # Create Load Balancer using above config objects

    $lb = New-AzureLoadBalancer `
        -Name $lbName `
        -ResourceGroupName $rgName `
        -Location $location `
        -FrontendIpConfiguration $lbFeIpConfig `
        -BackendAddressPool $lbBeIpPool `
        -Probe $lbProbe `
        -InboundNatRule $lbInboundNatRules `
        -LoadBalancingRule $lbRule

} else {

    # Get Load Balancer if already created

    $lb = Get-AzureLoadBalancer `
        -Name $lbName `
        -ResourceGroupName $rgName


13. Define Network Security Group Rules

If you need to further restrict inbound or outbound traffic for VMs, you can also define Network Security Group (NSG) firewall rules to permit only select network traffic.  These NSG rules can be assigned to a Subnet to restrict traffic for all VMs on a particular Subnet, or they can be assigned to individual NICs for specific VMs (which we’ll do in the next step). 

Here’s an example of the syntax for defining NSG rules using Azure Resource Manager:

# Define unique name for NSG resource

$nsgName = "${prefix}-nsg"

# Create NSG if it doesn't exist

if (!(Test-AzureResource `
    -ResourceName $nsgName `
    -ResourceType "Microsoft.Network/networkSecurityGroups" `
    -ResourceGroupName $rgName)) {

    # Sample rule – currently permits RDP from all source IPs

    $nsgRule1 = New-AzureNetworkSecurityRuleConfig `
        -Name "allow-rdp-inbound" `
        -Description "Allow Inbound RDP" `
        -SourceAddressPrefix * `
        -DestinationAddressPrefix * `
        -Protocol Tcp `
        -SourcePortRange * `
        -DestinationPortRange 3389 `
        -Direction Inbound `
        -Access Allow `
        -Priority 100

    # Sample rule – currently permits HTTP from all source IPs

    $nsgRule2 = New-AzureNetworkSecurityRuleConfig `
        -Name "allow-http-inbound" `
        -Description "Allow Inbound HTTP" `
        -SourceAddressPrefix * `
        -DestinationAddressPrefix * `
        -Protocol Tcp `
        -SourcePortRange * `
        -DestinationPortRange 80 `
        -Direction Inbound `
        -Access Allow `
        -Priority 110

   # Create NSG using rules defined above

    $nsg = New-AzureNetworkSecurityGroup `
        -Name $nsgName `
        -ResourceGroupName $rgName `
        -Location $location `
        -SecurityRules $nsgRule1, $nsgRule2 `
        -Tag $tags

} else {

    # Get NSG if already created

    $nsg = Get-AzureNetworkSecurityGroup `
        -Name $nsgName `
        -ResourceGroupName $rgName


14. Define Network Interfaces

Azure Resource Manager exposes Network Interfaces (aka vNICs) as separate resources distinct from VMs. This provides a great deal of flexibility by being able to assign network properties to vNICs that can then be attached to the appropriate VMs as needed by a configuration.

Let’s configure our Network Interface resources that we’ll later attach on a per VM basis.

# Define an empty array for holding NIC configs for each VM

$nics = @()

# Create each NIC if not already created

for ($count = 1; $count -le $vmInstances; $count++) {

    $nicName = "${prefix}-nic${count}"

    if (!(Test-AzureResource `
        -ResourceName $nicName `
        -ResourceType "Microsoft.Network/networkInterfaces" `
        -ResourceGroupName $rgName)) {

        $nicIndex = $count – 1

        # Attach each NIC to related Subnet, NSG rules,
        # NAT rules and Load Balancer pool
        $nics += New-AzureNetworkInterface `
            -Name $nicName `
            -ResourceGroupName $rgName `
            -Location $location `
            -SubnetId $vnet.Subnets[0].Id `
            -NetworkSecurityGroupId $nsg.Id `
            -LoadBalancerInboundNatRuleId `
                $lb.InboundNatRules[$nicIndex].Id `
            -LoadBalancerBackendAddressPoolId `

    } else {

        # Get each NIC if already created

        $nics += Get-AzureNetworkInterface `
            -Name $nicName `
            -ResourceGroupName $rgName



15. Create an Availability Set

In this scenario, we’ll be provisioning multiple load-balanced web server VMs, and we’ll want to make sure that VMs are distributed across Fault Domains and Update Domains for high availability. To provide a context for highly available placement within an Azure datacenter region, we’ll define an Availability Set resource, to which each load-balanced VM will be assigned in the next step.

# Create Availability Set if it doesn't exist

$avSetName = "${prefix}-as"

if (!(Test-AzureResource `
    -ResourceName $avSetName `
    -ResourceType "Microsoft.Compute/availabilitySets" `
    -ResourceGroupName $rgName)) {

    $avSet = New-AzureAvailabilitySet `
        -Name $avSetName `
        -ResourceGroupName $rgName `
        -Location $location

} else {

    # Get Availability Set if already created

    $avSet = Get-AzureAvailabilitySet `
        -Name $avSetName `
        -ResourceGroupName $rgName


16. Provision each Virtual Machine

Now that we have created the dependent resources needed for Virtual Machines, we can provision each VM from a common image using the snippet below.

# Select the VM Image Publisher
# ex. MicrosoftWindowsServer

$publisherName = `
    ( Get-AzureVMImagePublisher `
        -Location $location ).PublisherName |
    Out-GridView `
        -Title "Select a VM Image Publisher …" `

# Select the VM Image Offer
# ex. WindowsServer

$offerName = `
    ( Get-AzureVMImageOffer `
        -PublisherName $publisherName `
        -Location $location ).Offer |
    Out-GridView `
        -Title "Select a VM Image Offer …" `

# Select the VM Image SKU
# ex. 2012-R2-Datacenter

$skuName = `
    ( Get-AzureVMImageSku `
        -PublisherName $publisherName `
        -Offer $offerName `
        -Location $location ).Skus |
    Out-GridView `
        -Title "Select a VM Image SKU" `

# Select the VM Image Version
# ex. latest

$version = "latest"

# Select an Azure VM Instance Size
# ex. Standard_A3

$vmSize = `
    ( Get-AzureVMSize `
        -Location $location |
    Select-Object `
        Name, `
        NumberOfCores, `
        MemoryInMB, `
        MaxDataDiskCount |
    Out-GridView `
        -Title "Select a VM Instance Size" `
        -PassThru ).Name

# Specify VM local Admin credentials

$vmAdminCreds = Get-Credential `
    -Message "Enter Local Admin creds for new VMs …"

# For each VM, build configuration and provision

$vm = @()

for ($count = 1; $count -le $vmInstances; $count++) {
    $vmName = "vm${count}"

    if (!(Test-AzureResource `
        -ResourceName $vmName `
        -ResourceType "Microsoft.Compute/virtualMachines" `
        -ResourceGroupName $rgName)) {

        $vmIndex = $count – 1

        $osDiskLabel = "OSDisk"
        $osDiskName = "${prefix}-${vmName}-osdisk"

        $osDiskUri = `
$storageAccount.PrimaryEndpoints.Blob.ToString() `
            + "vhds/${osDiskName}.vhd"

        $dataDiskSize = 200 # Size in GB

        $dataDiskLabel = "DataDisk01"

        $dataDiskName = "${prefix}-${vmName}-datadisk01"

        $dataDiskUri = `
            $storageAccount.PrimaryEndpoints.Blob.ToString() `
            + "vhds/${dataDiskName}.vhd"

        $vmConfig = `
            New-AzureVMConfig `
                -VMName $vmName `
                -VMSize $vmSize `
                -AvailabilitySetId $avSet.Id |
            Set-AzureVMOperatingSystem `
                -Windows `
                -ComputerName $vmName `
                -Credential $vmAdminCreds `
                -ProvisionVMAgent `
                -EnableAutoUpdate |
            Set-AzureVMSourceImage `
                -PublisherName $publisherName `
                -Offer $offerName `
                -Skus $skuName `
                -Version $version |
            Set-AzureVMOSDisk `
                -Name $osDiskLabel `
                -VhdUri $osDiskUri `
                -CreateOption fromImage |
            Add-AzureVMDataDisk `
                -Name $dataDiskLabel `
                -DiskSizeInGB $dataDiskSize `
                -VhdUri $dataDiskURI `
                -CreateOption empty |
            Add-AzureVMNetworkInterface `
                -Id $nics[$vmIndex].Id `

        New-AzureVM `
            -VM $vmConfig `
            -ResourceGroupName $rgName `
            -Location $location `
            -Tags $tags


    # Get the VM if already provisioned

    $vm += Get-AzureVM `
        -Name $vmName `
        -ResourceGroupName $rgName


17. Configure VM Workloads with DSC

Now that our VMs are provisioned, we can use Desired State Configuration (DSC) to configure the operating system environment inside each VM for the intended workload.  In this example, we’ll use a fairly simple DSC configuration that installs the IIS Web Server feature and a Default Web Site.  We previously published this DSC configuration to an Azure storage account using the steps in this article.

# Set DSC archive and configuration values

$dscStorageAccountName = "keith02"

$dscArchiveName = "dscWebSiteConfig.ps1.zip"

$dscConfigFunction = "dscWebSiteConfig.ps1\WebSiteConfig"

$dscConfig = ConvertTo-Json -Depth 8 `

    SasToken = ""

    ModulesUrl = `

    ConfigurationFunction = "$dscConfigFunction"


# Apply DSC Configuration to each VM

$vm | ForEach-Object {

    $extensionName = $_.Name + "-dscExtension"

    Set-AzureVMExtension `
        -VMName $_.Name `
        -ResourceGroupName $_.ResourceGroupName `
        -Name $extensionName `
        -Location $location `
        -Publisher "Microsoft.PowerShell" `
        -ExtensionType "DSC" `
        -Version 2.0 `
        -SettingString $dscConfig


18. (Optional) Provision a Site-to-Site VPN Tunnel

If you’ll be connecting to your new Azure VM environment from your corporate offices, you may also prefer to configure a Site-to-Site IPsec Tunnel for secure connectivity.  In this step, we’ll provision an Azure Virtual Network Gateway as a Dynamic Routing Gateway to prepare the Azure VNET for establishing a Site-to-Site VPN tunnel connection to a corporate office location. 

Note: As of the publication date of this article, Virtual Network Gateway resources are only available in certain Azure datacenter regions.  Be sure to double-check the output from the Get-AzureLocation cmdlet in Step 7 above and ensure the datacenter region you’ve selected supports the “Microsoft.Network/virtualNetworkGateways” resource type before proceeding with the code snippet in this step.

# Define values for Gateways, Connection and Local Network

$vnetGatewayName = "${prefix}-gw"

$vnetGatewayIpConfigName = "${prefix}-gwip"

$vnetConnectionName = "${prefix}-gwcon"

$vnetConnectionKey = "shared-key-value"

$publicGatewayVipName = "${prefix}-gwvip"

$localGatewayName = "${prefix}-gwcorp"

$localGatewayIP = "Public-IPv4-of-local-gateway"

$localNetworkPrefix = @( "", "" )

# Create a Public VIP for VNET Gateway

if (!(Test-AzureResource `
    -ResourceName $publicGatewayVipName `
    -ResourceType "Microsoft.Network/publicIPAddresses" `
    -ResourceGroupName $rgName)) {

    $publicGatewayVip = New-AzurePublicIpAddress `
        -Name $publicGatewayVipName `
        -ResourceGroupName $rgName `
        -Location $location `
        -AllocationMethod Dynamic `
        -DomainNameLabel $vNetGatewayName `
        -Tag $tags

} else {

    $publicGatewayVip = Get-AzurePublicIpAddress `
        -Name $publicGatewayVipName `
        -ResourceGroupName $rgName


# Attach VNET Gateway to VIP & Subnet

$vnetGatewayIpConfig = `
    New-AzureVirtualNetworkGatewayIpConfig `
        -Name $vnetGatewayIpConfigName `
        -PublicIpAddressId $publicGatewayVip.Id `
        -PrivateIpAddress "" `
        -SubnetId $vnet.Subnets[1].Id

# Provision VNET Gateway

$vnetGateway = New-AzureVirtualNetworkGateway `
    -Name $vnetGatewayName `
    -ResourceGroupName $rgName `
    -Location $location `
    -GatewayType Vpn `
    -VpnType RouteBased `
    -IpConfigurations $vnetGatewayIpConfig `
    -Tag $tags

# Define Local Network

$localGateway = New-AzureLocalNetworkGateway `
    -Name $localGatewayName `
    -ResourceGroupName $rgName `
    -Location $location `
    -GatewayIpAddress $localGatewayIP `
    -AddressPrefix $localNetworkPrefix `
    -Tag $tags

# Define Site-to-Site Tunnel for Gateway

$vnetConnection = `
    New-AzureVirtualNetworkGatewayConnection `
        -Name $vnetConnectionName `
        -ResourceGroupName $rgName `
        -Location $location `
        -ConnectionType IPsec `
        -SharedKey $vnetConnectionKey `
        -Tag $tags `
        -VirtualNetworkGateway1 $vnetGateway `
        -LocalNetworkGateway2 $localGateway

After performing this step, you’ll still need to configure your on-premises VPN gateway device to complete the Site-to-Site VPN tunnel configuration. To complete the on-premises configuration for this tunnel, you will need to know the Public IP Address that has been assigned to your Azure Virtual Network Gateway.  You can determine this Public IP Address by running the following cmdlet:

# Get Public IP Address of Azure VNET Gateway

( Get-AzurePublicIpAddress `
        -Name $publicGatewayVipName `
        -ResourceGroupName $rgName ).IpAddress

Completed! What’s Next?

In this article, we’ve stepped through the process of using Azure Resource Manager, PowerShell and Desired State Configuration to provision an end-to-end IaaS VM environment on the Azure Cloud Platform. In future articles, we’ll continue our exploration of Azure Resource Manager by looking at PowerShell management snippets, ARM templates, and the Cross-Platform CLI for Linux and Mac OSX.

Until then … Hope to see you in the Cloud!