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.
Great one!!! xD. I will definitively put this into a test :)
LikeLike