How to use WMF 4 with Azure DSC Extension in Azure Cloud Service Manager (ASM)

Overview

In version 2.7 of the Azure DSC Extension, we added support to leave your Virtual Machine on the latest supported version of WMF 4.0. This blog will show you how to use this feature in Azure Cloud Service Manager (ASM). This assumes you already know how to create a VM in the Azure PowerShell SDK. If you don’t please see MSDN. We are working to add this feature into the Azure Powershell SDK DSC extension Cmdlets directly. Currently, the WMF 4 feature is only available if you form the JSON yourself and send it to the extension using the generic extension Cmdlet.

In this Example I will show you:

  1. How to Create the JSON you need to send to the extension.
  2. How to create URI to published configuration which will only let people with this URI access it, known as the `ModulesUrl’ to the extension.
  3. How to send this JSON to the extension on an existing VM.

Creating the JSON

To do this, use the Set-AzureVMExtension Cmdlet rather than the Set-AzureVMDscExtension Cmdlet and pass the JSON using the -PublicConfiguration parameter. The following function New-XAzureVmDscExtensionJson will create the JSON needed for this example.

# Create a Json to send the the DSC VM Extension
function New-XAzureVmDscExtensionJson
{
[CmdletBinding()]
    param(

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $moduleName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [string]
        $modulesUrl,

        [AllowNull()]
        [HashTable]
        $properties,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $configurationName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('4.0','latest','5.0PP')]
        [string]
        $WmfVersion
    )

    $publicSettingsTable = @{
        Properties = $properties
        WmfVersion = $WmfVersion
    }

    if($null -ne $modulesUrl)
    {
      $publicSettingsTable.Add('ModulesUrl',$modulesUrl)
    }

    $publicSettingsTable.Add('ConfigurationFunction' , "${ModuleName}\${configurationName}")

    return ConvertTo-Json -Depth 8 $publicSettingsTable
}

Here is an explanation of the values:

  • modulesUrl
    • Should be a URL to a zip file generated by Publish-AzureVmDscConfiguration
  • moduleName
    • The extension will look for this file name inside the zip file for the configuration.
  • configurationName
    • The extension will look for a Configuration with this function inside the file/module.
  • WmfVersion
    • The version of WMF to upgrade or leave the machine on. Currently supported values:
      • 4.0 indicating to upgrade to the currently supported version of WMF 4.0 if a newer version isn’t already installed.
      • 5.0PP indicating to upgrade to the WMF 5.0PP.
      • latest indicating to upgrade to the latest version of WMF.
  • properties
    • A Hash-table of parameters to be passed to the configuration

This function should produce a JSON that looks something like this.

"Properties":  {
                       "DestinationPath":  "C:\\test"
                   },
    "WmfVersion":  "4.0",
    "ConfigurationFunction":  "configuration.ps1\\ConfigurationName",
    "ModulesUrl":  "https://storageaccountname.blob.core.windows.net/windows-powershell-dsc/configuration.ps1.zip?<sastoken>"
}

Creating the `ModulesUrl’

To generate the JSON, we need the modulesUrl this is the URL of the published configuration, with any querystring needed to access the URL. The following function Get-XAzureDscPublishedModulesUrl will publish the configuration, create a read-only SASToken for 1 hour, and return the full URI to the blob with the SASToken.

# Publish a DSC configuration, Create a SasToken, and return the full URI with the SASToken
function Get-XAzureDscPublishedModulesUrl
{

  [CmdletBinding()]
  param
  (
    [Parameter(HelpMessage='The storage container to publish the configuration to')]
    [ValidateNotNullOrEmpty()]
    [String]
    $StorageContainer  = 'windows-powershell-dsc',

    [Parameter(Mandatory=$true, Position=0, HelpMessage='The name of the blob.')]
    [ValidateNotNullOrEmpty()]
    [String]
    $blobName,

    [Parameter(Mandatory=$true, Position=1, HelpMessage='The path to the configuration to publish')]
    [ValidateNotNullOrEmpty()]
    [String]
    $configurationPath,

    [Parameter(Mandatory=$true, Position=2, HelpMessage='The name of the storage account to publish to')]
    [ValidateNotNullOrEmpty()]
    [String]
    $storageAccountName
  )

  # Get the Storage Account Context
  function Get-AzureDscStorageAccountContext
  {
      param(
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $storageAccountName
      )
      $azureStorageAccount = Get-AzureStorageAccount -StorageAccountName $storageAccountName
      if(!$azureStorageAccount)
      {
        throw 'storage account not found'
      }

      $storageAccessKey      = (Get-AzureStorageKey -StorageAccountName $StorageAccountName).Primary
      $storageContext = New-AzureStorageContext -StorageAccountName $StorageAccountName `
            -StorageAccountKey $storageAccessKey

      return $storageContext
  }  

  $expiryTime = [DateTime]::UtcNow.AddMinutes(60)

  #Publish the configuration
  Publish-AzureVMDscConfiguration -ConfigurationPath $configurationPath -Verbose -Force `
      -storageContext (Get-AzureDscStorageAccountContext -storageAccountName $storageAccountName) `
      -ContainerName $StorageContainer

  # Create a SasToken for the Configuration
  return New-AzureStorageBlobSASToken -Container $StorageContainer -Blob $blobName -Permission r `
      -ExpiryTime $expiryTime -Context (Get-AzureDscStorageAccountContext -storageAccountName $storageAccountName) -FullUri
}

Putting it all together and Sending it to a VM

Now we need to call the function to get the modulesUrl, pass the value to the function to get the JSON along with the rest of the parameters, get our VM object, and call Set-AzureVMExtension on the VM with the JSON and the parameter to send it the the DSC extension. The following is an example of how to do that.

$storageAccountName = 'storageaccountname'
$publisher          = 'Microsoft.Powershell'
$dscVersion         = '2.7'
$serviceName        = 'servicename'
$vmName             = 'vmName'
$moduleName         = 'configuration.ps1'
$blobName           = "$moduleName.zip"
$configurationPath  = "$PSScriptRoot\$moduleName"
$ConfigurationName  = 'ConfigurationName'

$modulesUrl = Get-XAzureDscPublishedModulesUrl -blobName $blobName -configurationPath $configurationPath `
   -storageAccountName $storageAccountName
Write-Verbose -Message "ModulesUrl: $modulesUrl" -Verbose

$PublicConfigurationJson = New-XAzureVmDscExtensionJson -moduleName $moduleName -modulesUrl $modulesUrl `
    -properties @{DestinationPath = 'C:\test'} -configurationName $ConfigurationName -WmfVersion '4.0' -Verbose
Write-Verbose -Message "PublicConfigurationJson: $PublicConfigurationJson" -Verbose

$vm = get-azurevm -ServiceName $serviceName -Name $vmName
$vm = Set-AzureVMExtension `
        -VM $vm `
        -Publisher $publisher `
        -ExtensionName 'DSC' `
        -Version $dscVersion `
        -PublicConfiguration $PublicConfigurationJson `
        -ForceUpdate

$vm | Update-AzureVM

After the VM is finished update, you should have a WMF 4 VM. I’ll have a follow-up blog on how to do this in ARM.

I have published these samples to GitHub as a working script. Just update the, Service Name, etc. and run the script in the Azure PowerShell SDK.

Notes

Windows Server 2016 Technical Preview has the equivalent of WMF 5 already installed. Therefore, specifying WMF 4 for this OS is not a valid option.