A Monster VM Azure Resource Manager Template

In April I wrote a post about building a monster Virtual Machine using PowerShell on Microsoft Azure. Since then, Microsoft has released version 2 of the Azure Resource Manager (ARM). This version allows us to define a Virtual Machine, its data disks and its Desired State Configuration (DSC) VM Extensions as a template. Seeing this as a great opportunity, I decided to convert my first PowerShell script to an ARM template that would create a Virtual Machine and striped data disk.

The Target Virtual Machine Configuration

16 Cores
112 GB of RAM
800 GB of local SSD for temp disk
32 TB for the data disk
50,000 IOPS for the data disk
512 MB per second for the data disk

What is the Azure Resource Manager (ARM)?

Most applications that run in Microsoft Azure use a combination of resources (such as a database server, database, or website) to perform as designed. An Azure Resource Manager Template makes it possible for you to deploy and manage these resources together by using a JSON description of the resources and associated deployment parameters.

Azure Resource Manager Template

Using the Windows Server Virtual Machine template as a base, I delved into various templates from the Azure Quickstart Templates. These templates helped me figure out how to create an OS Disk in an Azure Premium Storage Account and how to attach thirty-two 1TB data disks.

The following is the template that resulted from this exercise. In order to, navigate and build this template, I used the Visual Studio JSON Explorer.

WindowsVirtualMachine.json

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.1",
    "parameters": {
        "newStorageAccountName": {
            "type": "string",
            "metadata": {
                "description": "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed."
            }
        },
        "adminUsername": {
            "type": "string",
            "metadata": {
                "description": "Username for the Virtual Machine."
            }
        },
        "adminPassword": {
            "type": "securestring",
            "metadata": {
                "description": "Password for the Virtual Machine."
            }
        },
        "location": {
            "type": "string",
            "metadata": {
                "description": "Data Center"
            },
            "defaultValue": "West US",
            "allowedValues": [
                "West US",
                "East US 2",
                "West Europe",
                "Southeast Asia",
                "Japan West"
            ]
        },
        "vmSize": {
            "type": "string",
            "metadata": {
                "description": "Virtual Machine Size"
            },
            "defaultValue": "Standard_DS14",
            "allowedValues": [
                "Standard_DS14"
            ]
        },
        "vmName": {
            "type": "string",
            "metadata": {
                "description": "Unique Name for the Virtual Machine"
            }
        },
        "dnsNameForPublicIP": {
            "type": "string",
            "metadata": {
                "description": "Unique DNS Name for the Public IP used to access the Virtual Machine."
            }
        },
        "windowsOSVersion": {
            "type": "string",
            "defaultValue": "2012-R2-Datacenter",
            "allowedValues": [
                "2008-R2-SP1",
                "2012-Datacenter",
                "2012-R2-Datacenter",
                "Windows-Server-Technical-Preview"
            ],
            "metadata": {
                "description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version. Allowed values: 2008-R2-SP1, 2012-Datacenter, 2012-R2-Datacenter, Windows-Server-Technical-Preview."
            }
        },
        "modulesUrl": {
            "type": "string",
            "metadata": {
                "description": "URL for the DSC configuration module. NOTE: Can be a Github url(raw) to the zip file"
            }
        },
        "configurationFunction": {
            "type": "string",
            "metadata": {
                "description": "DSC configuration function to call"
            }
        }
    },
    "variables": {
        "imagePublisher": "MicrosoftWindowsServer",
        "imageOffer": "WindowsServer",
        "OSDiskName": "osdisk",
        "sizeOfDiskInGB": "1023",
        "nicName": "VMNic",
        "addressPrefix": "10.0.0.0/16",
        "subnetName": "Subnet",
        "subnetPrefix": "10.0.0.0/24",
        "storageAccountType": "Premium_LRS",
        "publicIPAddressName": "PublicIP",
        "publicIPAddressType": "Dynamic",
        "vmStorageAccountContainerName": "vhds",
        "virtualNetworkName": "[concat(parameters('vmName'),'vnet')]",
        "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]",
        "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]"
    },
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "name": "[parameters('newStorageAccountName')]",
            "apiVersion": "2015-05-01-preview",
            "location": "[parameters('location')]",
            "tags": {
                "displayName": "StorageAccount"
            },
            "properties": {
                "accountType": "[variables('storageAccountType')]"
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/publicIPAddresses",
            "name": "[variables('publicIPAddressName')]",
            "location": "[parameters('location')]",
            "tags": {
                "displayName": "PublicIPAddress"
            },
            "properties": {
                "publicIPAllocationMethod": "[variables('publicIPAddressType')]",
                "dnsSettings": {
                    "domainNameLabel": "[parameters('dnsNameForPublicIP')]"
                }
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/virtualNetworks",
            "name": "[variables('virtualNetworkName')]",
            "location": "[parameters('location')]",
            "tags": {
                "displayName": "VirtualNetwork"
            },
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "[variables('addressPrefix')]"
                    ]
                },
                "subnets": [
                    {
                        "name": "[variables('subnetName')]",
                        "properties": {
                            "addressPrefix": "[variables('subnetPrefix')]"
                        }
                    }
                ]
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Network/networkInterfaces",
            "name": "[variables('nicName')]",
            "location": "[parameters('location')]",
            "tags": {
                "displayName": "NetworkInterface"
            },
            "dependsOn": [
                "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
                "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]"
            ],
            "properties": {
                "ipConfigurations": [
                    {
                        "name": "ipconfig1",
                        "properties": {
                            "privateIPAllocationMethod": "Dynamic",
                            "publicIPAddress": {
                                "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]"
                            },
                            "subnet": {
                                "id": "[variables('subnetRef')]"
                            }
                        }
                    }
                ]
            }
        },
        {
            "apiVersion": "2015-05-01-preview",
            "type": "Microsoft.Compute/virtualMachines",
            "name": "[parameters('vmName')]",
            "location": "[parameters('location')]",
            "tags": {
                "displayName": "VirtualMachine"
            },
            "dependsOn": [
                "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]",
                "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
            ],
            "properties": {
                "hardwareProfile": {
                    "vmSize": "[parameters('vmSize')]"
                },
                "osProfile": {
                    "computername": "[parameters('vmName')]",
                    "adminUsername": "[parameters('adminUsername')]",
                    "adminPassword": "[parameters('adminPassword')]"
                },
                "storageProfile": {
                    "imageReference": {
                        "publisher": "[variables('imagePublisher')]",
                        "offer": "[variables('imageOffer')]",
                        "sku": "[parameters('windowsOSVersion')]",
                        "version": "latest"
                    },
                    "osDisk": {
                        "name": "osdisk",
                        "vhd": {
                            "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
                        },
                        "caching": "ReadWrite",
                        "createOption": "FromImage"
                    },
                    "dataDisks": [
                        {
                            "name": "datadisk1",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 0,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','1','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk2",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 1,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','2','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk3",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 2,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','3','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk4",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 3,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','4','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk5",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 4,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','5','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk6",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 5,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','6','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk7",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 6,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','7','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk8",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 7,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','8','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk9",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 8,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','9','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk10",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 9,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','10','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk11",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 10,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','11','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk12",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 11,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','12','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk13",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 12,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','13','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk14",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 13,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','14','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk15",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 14,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','15','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk16",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 15,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','16','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk17",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 16,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','17','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk18",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 17,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','18','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk19",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 18,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','19','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk20",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 19,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','20','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk21",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 20,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','21','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk22",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 21,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','22','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk23",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 22,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','23','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk24",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 23,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','24','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk25",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 24,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','25','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk26",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 25,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','26','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk27",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 26,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','27','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk218",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 27,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','28','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk29",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 28,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','29','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk30",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 29,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','30','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk31",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 30,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','31','.vhd')]"
                            },
                            "createOption": "Empty"
                        },
                        {
                            "name": "datadisk32",
                            "diskSizeGB": "[variables('sizeOfDiskInGB')]",
                            "lun": 31,
                            "caching": "ReadOnly",
                            "vhd": {
                                "Uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/','datadisk','32','.vhd')]"
                            },
                            "createOption": "Empty"
                        }
                    ]
                },
                "networkProfile": {
                    "networkInterfaces": [
                        {
                            "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
                        }
                    ]
                }
            }
        },
        {
            "type": "Microsoft.Compute/virtualMachines/extensions",
            "name": "[concat(parameters('vmName'),'/', 'dscExtension')]",
            "apiVersion": "2015-05-01-preview",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'))]"
            ],
            "properties": {
                "publisher": "Microsoft.Powershell",
                "type": "DSC",
                "typeHandlerVersion": "1.7",
                "settings": {
                    "ModulesUrl": "[parameters('modulesUrl')]",
                    "SasToken": "",
                    "ConfigurationFunction": "[parameters('configurationFunction')]",
                    "Properties": {
                        "StoragePoolFriendlyName" : "storagespace0",
                        "VirtualDiskFriendlyName" : "datadisk",
                        "DriveSize": 35184372088832,
                        "NumberOfColumns": 32
                    }
                },
                "protectedSettings": null
            }
        }
    ]
}

Azure Resource Manager Template Parameters

This file contains deployment specific parameter values for the Azure Resource Manager Template. As the name suggests, we can have multiple parameter files per project. If a parameter is missing from this file, it will be requested when the deployment is initiated.

WindowsVirtualMachine.param.dev.json

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.1",
    "parameters": {
        "newStorageAccountName": {
            "value": "msarmbrisebois"
        },
        "adminUsername": {
            "value": "brisebois"
        },
        "dnsNameForPublicIP": {
            "value": "msarmbrisebois"
        },
        "vmName": {
            "value": "msarmbrisebois"
        },
        "windowsOSVersion": {
            "value": "2012-R2-Datacenter"
        },
        "configurationFunction": {
            "value": "DiskConfiguration.ps1\\DiskConfiguration"
        }
    }
}

Creating a Custom DSC

This custom Desired State Configuration (DSC) stripes 32 Premium Data Disks using the DSC Virtual Machine Extension. To create this Module Iets borrowed from my earlier post that create a custom DSC Module to Stripe data disks on Azure Virtual Machines. First we need to make a few changes. We will start by breaking each step into its own module. Then we will add better test logic so that if one step fails, the DSC can be executed again.

The following scaffolding script builds out the files and folder structure for the DSC that we are building

Scaffolding the DSC Module

New-xDscResource –Name cCreateStorageSpace `
                 -Property (New-xDscResourceProperty –Name FriendlyName `
                                                     –Type String `
                                                     -Attribute Key) `
                 -Path 'C:\Program Files\WindowsPowerShell\Modules\' `
                 -ModuleName cDiskTools `
                 -FriendlyName StorageSpace `
                 -Verbose

New-xDscResource –Name cCreateVirtualDisk `
                 -Property (New-xDscResourceProperty –Name FriendlyName `
                                                     –Type String `
                                                     -Attribute Key), `
                           (New-xDscResourceProperty –Name StoragePoolFriendlyName `
                                                     –Type String `
                                                     -Attribute Required),
                           (New-xDscResourceProperty –Name DriveSize `
                                                     –Type UInt64 `
                                                     -Attribute Required), `
                           (New-xDscResourceProperty –Name NumberOfColumns `
                                                     -Type Uint32 `
                                                     -Attribute Required) `
                 -Path 'C:\Program Files\WindowsPowerShell\Modules\' `
                 -ModuleName cDiskTools `
                 -FriendlyName VirtualDisk `
                 -Verbose

New-xDscResource –Name cInitializeDisk `
                 -Property (New-xDscResourceProperty –Name VirtualDiskFriendlyName `
                                                     –Type String `
                                                     -Attribute Key) `
                 -Path 'C:\Program Files\WindowsPowerShell\Modules\' `
                 -ModuleName cDiskTools `
                 -FriendlyName Disk `
                 -Verbose

New-xDscResource –Name cCreateDiskPartition `
                 -Property (New-xDscResourceProperty –Name VirtualDiskFriendlyName `
                                                     –Type String `
                                                     -Attribute Key) `
                 -Path 'C:\Program Files\WindowsPowerShell\Modules\' `
                 -ModuleName cDiskTools `
                 -FriendlyName DiskPartition `
                 -Verbose

Now that we have something to work with, let’s port the code from DSC I created for my earlier post. The following files reside in the following location C:\Program Files\WindowsPowerShell\Modules\cDiskTools\

DSCResources\cCreateStorageSpace\cCreateStorageSpace.psm1

The first module tests whether the storage space already exists. If it’s missing, the module will create it.

function Get-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Collections.Hashtable])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$FriendlyName
	)
}

function Set-TargetResource
{
	[CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$FriendlyName
	)

    New-StoragePool -FriendlyName $FriendlyName `
                    -StorageSubSystemUniqueId (Get-StorageSubSystem -FriendlyName '*Space*').uniqueID `
                    -PhysicalDisks (Get-PhysicalDisk -CanPool $true)
}

function Test-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Boolean])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$FriendlyName
	)

    $result = [System.Boolean]

    try
    {
        $pool = Get-StoragePool -FriendlyName $FriendlyName -ErrorAction Ignore
        if(!$pool){
            $result = $false
        }else{
            $result = $true
        }
    }
    catch [System.Exception]
    {
        $result = $false
    }

    $result
}

Export-ModuleMember -Function *-TargetResource

DSCResources\cCreateVirtualDisk\cCreateVirtualDisk.psm1

The second module will check whether the virtual disk already exists. If it’s missing it will create it using the provided parameters.

function Get-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Collections.Hashtable])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$FriendlyName,

		[parameter(Mandatory = $true)]
		[System.String]
		$StoragePoolFriendlyName,

		[parameter(Mandatory = $true)]
		[System.UInt64]
		$DriveSize,

		[parameter(Mandatory = $true)]
		[System.UInt32]
		$NumberOfColumns
	)

}

function Set-TargetResource
{
	[CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$FriendlyName,

		[parameter(Mandatory = $true)]
		[System.String]
		$StoragePoolFriendlyName,

		[parameter(Mandatory = $true)]
		[System.UInt64]
		$DriveSize,

		[parameter(Mandatory = $true)]
		[System.UInt32]
		$NumberOfColumns
	)

    Write-Debug $FriendlyName

	New-VirtualDisk -FriendlyName $FriendlyName `
                    -StoragePoolFriendlyName $StoragePoolFriendlyName `
                    -Size $DriveSize `
                    -NumberOfColumns $NumberOfColumns `
                    -ProvisioningType Thin `
                    -ResiliencySettingName Simple

}

function Test-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Boolean])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$FriendlyName,

		[parameter(Mandatory = $true)]
		[System.String]
		$StoragePoolFriendlyName,

		[parameter(Mandatory = $true)]
		[System.UInt64]
		$DriveSize,

		[parameter(Mandatory = $true)]
		[System.UInt32]
		$NumberOfColumns
	)

    Write-Debug $FriendlyName

    $result = [System.Boolean]

    try
    {
        $disk = Get-VirtualDisk -FriendlyName $FriendlyName -ErrorAction Ignore
        if(!$disk){
            $result = $false
        }else{
            $result = $true
        }
    }
    catch [System.Exception]
    {
        $result = $false
    }

    Write-Debug $result

    $result
}

Export-ModuleMember -Function *-TargetResource

DSCResources\cInitializeDisk\cInitializeDisk.psm1

This module then makes sure that the disk is initialized properly.

function Get-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Collections.Hashtable])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VirtualDiskFriendlyName
	)

}

function Set-TargetResource
{
	[CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VirtualDiskFriendlyName
	)

    Initialize-Disk -VirtualDisk (Get-VirtualDisk -FriendlyName $VirtualDiskFriendlyName) -PartitionStyle GPT -Confirm:$false
}

function Test-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Boolean])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VirtualDiskFriendlyName
	)

	$result = [System.Boolean]

    try
    {
        $virtualDisk = Get-VirtualDisk -FriendlyName $VirtualDiskFriendlyName -ErrorAction Stop
        $disk = Get-Disk -VirtualDisk $virtualDisk

        if($disk.PartitionStyle -eq 'GPT')
        {
           $result = $true
        }
        else
        {
           $result = $false
        }
    }
    catch [System.Exception]
    {
        $result = $false
    }

    $result
}

Export-ModuleMember -Function *-TargetResource

DSCResources\cCreateDiskPartition\cCreateDiskPartition.psm1

Finally, this module creates a partition, assigns a drive letter and formats it.

function Get-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Collections.Hashtable])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VirtualDiskFriendlyName
	)
}

function Set-TargetResource
{
	[CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VirtualDiskFriendlyName
	)

    Get-VirtualDisk -FriendlyName $VirtualDiskFriendlyName `
    | Get-Disk `
    | New-Partition -UseMaximumSize `
                    -AssignDriveLetter `
    | Format-Volume
}

function Test-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Boolean])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VirtualDiskFriendlyName
	)

    $result = [System.Boolean]

    try
    {
        $virtualDisk = Get-VirtualDisk -FriendlyName $VirtualDiskFriendlyName -ErrorAction Stop
        $disk = Get-Disk -VirtualDisk $virtualDisk

        $partition = Get-Partition -DiskNumber $disk.Number

        if(!$partition.DriveLetter)
        {
           $result = $false
        }
        else
        {
           $result = $true
        }
    }
    catch [System.Exception]
    {
        $result = $false
    }

    $result
}

Export-ModuleMember -Function *-TargetResource

DSC Configuration

This configuration executes the DSC Modules listed above and specifies the total size of the drive that will be created and the number of columns to use. On Azure, the rule of thumb is 1 column per data disk.

configuration DiskConfiguration {
 param (
   [String]$StoragePoolFriendlyName,
   [String]$VirtualDiskFriendlyName,
   [UInt64]$DriveSize = 32TB,
   [UInt32]$NumberOfColumns = 32
 )

 Import-DscResource -Module cDiskTools

 node localhost
 {
   Settings
   {
      DebugMode = 'All,ResourceScriptBreakAll'
      ConfigurationMode = 'ApplyAndAutoCorrect'
      RefreshMode = 'Disabled'
   }

   StorageSpace CreateStorageSpace
   {
     FriendlyName = $StoragePoolFriendlyName
   }

   VirtualDisk CreateVirtualDisk
   {
     FriendlyName = $VirtualDiskFriendlyName
     StoragePoolFriendlyName = $StoragePoolFriendlyName
     DriveSize = $DriveSize
     NumberOfColumns = $NumberOfColumns
   }

   Disk InitializeDisk
   {
     VirtualDiskFriendlyName = $VirtualDiskFriendlyName
   }

   DiskPartition CreateDiskPartition
   {
     VirtualDiskFriendlyName = $VirtualDiskFriendlyName
   }
 }
}

Packaging and Deployment

Getting the PowerShell DSC in Azure Storage can be done using the Publish-AzureVMDscConfiguration cmdlet. It can also be done by creating a zip file containing the DSC Module and the DSC Configuration. Then it’s up to you to upload the zip file to Azure Blob Storage. In the end the cmdlet is quite useful when you need to iterate quickly.

Switch-AzureMode AzureServiceManagement

$ErrorActionPreference = "stop"

# set the storage account that will contain the DSC packages

Set-AzureSubscription -SubscriptionName (Get-AzureSubscription -Current).SubscriptionName -CurrentStorageAccountName 'briseboispackages'

# package and publish the DSC to the storage account

Publish-AzureVMDscConfiguration -ConfigurationPath 'C:\Program Files\WindowsPowerShell\Modules\cDiskTools\DiskConfiguration.ps1' -Force

# 

$currentStorageAccount = (Get-AzureSubscription -Current).CurrentStorageAccountName

$ModuleURL = "https://$currentStorageAccount.blob.core.windows.net/windows-powershell-dsc/DiskConfiguration.ps1.zip"

$additionalParameters = New-Object -TypeName Hashtable

$additionalParameters["modulesUrl"] = $ModuleURL

Switch-AzureMode AzureResourceManager

# deploy the monster Virtual Machine

New-AzureResourceGroup -Name "armmonstervm" `
                       -Location "westus" `
                       -TemplateFile 'WindowsVirtualMachine.json' `
                       -TemplateParameterFile 'WindowsVirtualMachine.param.dev.json' `
                       @additionalParameters `
                       -Force -Verbose

The deployment produces the following output, that allows us to determine where things might have gone wrong.

VERBOSE: 5:04:36 PM - Created resource group 'armmonstervm' in location 'westus'
VERBOSE: 5:04:36 PM - Template is valid.
VERBOSE: 5:04:37 PM - Create template deployment 'WindowsVirtualMachine'.
VERBOSE: 5:04:42 PM - Resource Microsoft.Network/publicIPAddresses 'PublicIP' provisioning status is running
VERBOSE: 5:04:42 PM - Resource Microsoft.Network/virtualNetworks 'msarmbriseboisvnet' provisioning status is running
VERBOSE: 5:04:45 PM - Resource Microsoft.Storage/storageAccounts 'msarmbrisebois' provisioning status is running
VERBOSE: 5:04:54 PM - Resource Microsoft.Network/virtualNetworks 'msarmbriseboisvnet' provisioning status is succeeded
VERBOSE: 5:04:57 PM - Resource Microsoft.Network/publicIPAddresses 'PublicIP' provisioning status is succeeded
VERBOSE: 5:04:59 PM - Resource Microsoft.Network/networkInterfaces 'VMNic' provisioning status is succeeded
VERBOSE: 5:08:20 PM - Resource Microsoft.Compute/virtualMachines 'msarmbrisebois' provisioning status is running
VERBOSE: 5:08:20 PM - Resource Microsoft.Storage/storageAccounts 'msarmbrisebois' provisioning status is succeeded
VERBOSE: 5:22:03 PM - Resource Microsoft.Compute/virtualMachines/extensions 'msarmbrisebois/dscExtension' provisioning status is running
VERBOSE: 5:22:03 PM - Resource Microsoft.Compute/virtualMachines 'msarmbrisebois' provisioning status is succeeded
VERBOSE: 5:29:14 PM - Resource Microsoft.Compute/virtualMachines/extensions 'msarmbrisebois/dscExtension' provisioning status is succeeded

In a subsequent post, I will detail the steps that helped me to test and debug the Desired State Configuration used in this template.

5 responses to Using Azure Resource Manager (ARM) to Deploy a Monster VM

  1. 

    Great one!!! xD. I will definitively put this into a test :)

    Like

Trackbacks and Pingbacks:

  1. Debugging Desired State Configuration (DSC) on #Azure VMs « Alexandre Brisebois ☁ - May 20, 2015

    […] previous post was about using the Azure Resource Manager to provision a Virtual Machine. It demonstrated how to use a custom PowerShell Desired State Configuration (DSC) to stripe, format […]

    Like

  2. Step-by-step: My first Azure Resource Group Template - ITPrOla - Site Home - TechNet Blogs - June 1, 2015

    […] A Monster VM Azure Resource Manager Template: https://alexandrebrisebois.wordpress.com/2015/05/16/using-azure-resource-manager-arm-to-deploy-a-mon… […]

    Like

  3. Troubleshooting – My Azure Virtual Machine Won’t Start « Alexandre Brisebois ☁ - October 12, 2015

    […] Azure Resource Manager to provision a new Windows Virtual Machine and Desired State Configuration (DSC) to complete the […]

    Like

  4. Build it, Use it, Destroy it! « Alexandre Brisebois ☁ - October 17, 2015

    […] Leverage Azure Resource Manager to provision a new Windows Virtual Machine and use Desired State Configuration (DSC) for the final steps. […]

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.