Search code examples
azureazure-resource-managerazure-vm-templates

How can I attach a data disk to a Windows Server Azure VM and format it directly in the template?


I need to attach data disks to VMs (in a VMSS) and like to instantly format and use the disk w/o further manual intervention. How can I achieve this directly in an ARM template?


Solution

  • I added 3 parameters to the ARM template:

    ...
    "scriptLocation": {
      "type": "string",
      "metadata": {
        "description": "Location of custom extension scripts on storage account container"
      }
    },
    "scriptStorageAccount": {
      "type": "string",
      "metadata": {
        "description": "Name of custom extension scripts storage account"
      }
    },
    "scriptStorageAccountKey": {
      "type": "string",
      "metadata": {
        "description": "Key to custom extension scripts storage account"
      }
    },
    ...
    

    These parameters are populated in the PowerShell script uploading the custom extension script file and calling the New-AzureRmResourceGroupDeployment.

    ...
    $StorageAccountName = "mydeploymentstorage"
    $StorageContainerName = "ext"
    $ArtifactStagingDirectory = ".\ExtensionScripts"
    ...
    # transfer Extension script to Storage    $StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName})
    $StorageAccountContext = $StorageAccount.Context
    New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccountContext -Permission Container -ErrorAction SilentlyContinue *>&1
    $ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName}
    foreach ($SourcePath in $ArtifactFilePaths) {
        Write-Host "transfering" $SourcePath
        $BlobName = $SourcePath.Substring($SourcePath.LastIndexOf("\")+1)
        Set-AzureStorageBlobContent -File $SourcePath -Blob $BlobName -Container $StorageContainerName -Context $StorageAccountContext -Force -ErrorAction Stop
    }
    
    # prepare and pass script parameters
    $DynamicParameters = New-Object -TypeName Hashtable
    $DynamicParameters["scriptLocation"] = $StorageAccountContext.BlobEndPoint + $StorageContainerName
    $DynamicParameters["scriptStorageAccount"] = $StorageAccountName
    $DynamicParameters["scriptStorageAccountKey"] = ($StorageAccount | Get-AzureRmStorageAccountKey).Value[0]
    ...
    # start deployment
    New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` `
        -ResourceGroupName $ResourceGroupName `
        -TemplateFile $TemplateFile `
        -TemplateParameterFile $TemplateParametersFile `
        @DynamicParameters `
        -Verbose
    

    In the VMSS extensionProfile I added the custom script extension (to have it in one place with the other extensions):

    ...
          "storageProfile": {
            "imageReference": {
              "publisher": "[parameters('vmImagePublisher')]",
              "offer": "[parameters('vmImageOffer')]",
              "sku": "[parameters('vmImageSku')]",
              "version": "[parameters('vmImageVersion')]"
            },
            "osDisk": {
              "caching": "ReadWrite",
              "createOption": "FromImage",
              "managedDisk": {
                "storageAccountType": "[parameters('storageAccountType')]"
              }
            },
            "dataDisks": [
              {
                "diskSizeGB": 128,
                "lun": 0,
                "createOption": "Empty",
                "managedDisk": {
                  "storageAccountType": "[parameters('storageAccountType')]"
                }
              }
            ]
          }
    ...
        "virtualMachineProfile": {
          "extensionProfile": {
            "extensions": [
    ...
              {
                "name": "[concat(parameters('vmNodeType0Name'),'_CreateDisk')]",
                "properties": {
                  "publisher": "Microsoft.Compute",
                  "type": "CustomScriptExtension",
                  "typeHandlerVersion": "1.9",
                  "autoUpgradeMinorVersion": true,
                  "settings": {
                    "fileUris": [
                      "[concat(parameters('scriptLocation'),'/CreateDisk.ps1')]"
                    ]
                  },
                  "protectedSettings": {
                    "commandToExecute": "powershell -ExecutionPolicy Unrestricted -File CreateDisk.ps1",
                    "storageAccountName": "[parameters('scriptStorageAccount')]",
                    "storageAccountKey": "[parameters('scriptStorageAccountKey')]"
                  }
                }
              }
            ]
    

    And then finally created the script. My initial problem had been, that I did not have enough space on C: for a ...-smalldisk VM SKU to hold all docker images, so I moved docker to the new drive.

    # create and format disk
    
    Get-Disk |
        Where PartitionStyle -eq 'Raw' |
        Select-Object -First 1 |
        Initialize-Disk -PartitionStyle MBR -PassThru |
        New-Partition -DriveLetter F -UseMaximumSize |
        Format-Volume -FileSystem NTFS -NewFileSystemLabel "Containers" -Confirm:$false
    
    # move docker to F:\docker
    
    docker images -a -q | %{docker rmi $_ --force}
    
    Stop-Service Docker
    $service = (Get-Service Docker)
    $service.WaitForStatus("Stopped","00:00:30")
    
    @{"data-root"="F:\docker"} | ConvertTo-Json | Set-Content    C:\programdata\docker\config\daemon.json
    Get-Process docker* | % {Stop-Process -Id $_.Id -Force}
    docker system info
    
    Copy-Item C:\programdata\docker F:\docker -Recurse
    
    Start-Service Docker