PowerShell Desired State Configuration (DSC) is a new Powershell feature shipped with Windows Server 2012 R2 and Windows 8.1. In short, it allows you to specify what a machine configuration should look like and leaves it to the so-called Local Configuration Manager on the target machine to get into this desired state. There are plenty of resources available if you'd like to learn more so I won't get into Powershell DSC details here. I can particularly advise the Powershell DSC Book from PowerShell.org.
I was configuring an Azure Virtual Network (VNet) through PowerShell as part of a larger script and thought it would be a nice exercise to write a basic PowerShell DSC resource for configuring the network. PowerShell options for configuring an Azure Virtual Network are quite limited: the most important cmdlet is Set-AzureVnetConfig
that accepts an XML file that must describe your entire network configuration. There are no cmdlets for adding a virtual network site or DNS server, for example. This is somewhat limiting but it makes the initial job of writing a DSC resource a lot easier.
First, you should download the latest PowerShell DSC Resource Kit (at the time of writing this is Wave 5). It contains two things we need among a lot of other DSC resources: the xAzure
module containing the xAzureSubscription
DSC resource for setting up an Azure subscription before configuring our virtual network and the xDscResourceDesigner
module that allows easy creation of DSC resources. Unzip the resource kit to C:Program FilesWindowsPowerShellModules
like this:
The DSC resource we're creating mimics the behavior of the Set-AzureVnetConfig
in that it only accepts a path to the .netcfg
file. Besides we add one additional property for later use: the current XML configuration. The script to create the DSC resource template is as follows (run as administrator):
Import-Module xDSCResourceDesigner $ConfigurationPath = New-xDscResourceProperty -Name ConfigurationPath -Type String -Attribute Key $CurrentVNetConfig = New-xDscResourceProperty -Name CurrentVNetConfig -Type String -Attribute Write New-xDscResource -Name cAzureVNetConfig ` -Property $ConfigurationPath,$CurrentVNetConfig ` -Path 'C:Program FilesWindowsPowerShellModules' ` -ModuleName cAzure -FriendlyName cAzureVNetConfig -Force
Running this script results in the following files and folders in C:Program FilesWindowsPowerShellModules
:
C:...cAzurecAzure.psd1 DSCResourcescAzureVNetConfigcAzureVNetConfig.psm1 cAzureVNetConfig.schema.mof
The cAzure.psd1
file is the module manifest. The cAzureVNetConfig.schema.mof
file is the WMI Managed Object Format file that describes our resource to WMI. And finally cAzureVNetConfig.psm1
that contains the actual scripts for the DSC resource. It consists of three functions:
Get-TargetResource
: this function usually retrieves the current state of the target system. For example, the built-in File DSC resource may check to see whether a specific file or directory exists and return this information.Set-TargetResource
: this function is responsible for manipulating the target system so that it gets into desired state.Test-TargetResource
: this function should return$true
or$false
, indicating whether the target system is in the desired state or not.
Let's start with the simplest: Test-TargetResource
. For now, we just let it return $false
, indicating that the Azure Virtual Network is not in the desired state. We could implement a comparison between the XML document we provide and the current configuration to check for differences but all that would gain us is preventing an unnecessary Azure network configuration update. So Test-TargetResource
is as follows:
function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [System.String]$ConfigurationPath, [parameter()] [System.String]$CurrentVNetConfig ) $result = $false $result }
Next is Get-TargetResource
. It's main job is to get the current network configuration, although we don't do anything with it in this first version:
function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $true)] [System.String]$ConfigurationPath ) #Get current VNet config. $currentVNetConfig = (Get-AzureVNetConfig).XMLConfiguration #Return both configuration path and the current configuration. $returnValue = @{ ConfigurationPath = $ConfigurationPath CurrentVNetConfig = $currentVNetConfig } $returnValue }
The function takes one parameter: the configuration path for our new network configuration and it returns a hash table with the configuration path and the current configuration.
The last function we need is Set-TargetResource
which applies our new network configuration:
function Set-TargetResource { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [System.String]$ConfigurationPath, [parameter()] [System.String]$CurrentVNetConfig ) #Set the new VNet config from the specified configuration path. $operation = Set-AzureVNetConfig -ConfigurationPath $ConfigurationPath -Verbose -Debug Write-Verbose "Setting Azure VNet config result: $operation.OperationStatus" }
It takes two parameters, the configuration path and the current configuration and then calls Set-AzureVNetConfig
using the specified configuration path. Pretty easy, all combined. Now on to how to use all of this. And a tip: before using our new resource, despite it being in the correct folder (on PSModulePath
), you may need to reboot. I have no idea why.
To use the new resource you have to write a configuration, use the configuration to generate a MOF file for our target system and apply the MOF file. The target system in this case is localhost
since we run the configuration from the current machine. To be able to retrieve the current Azure VNet config you need an Azure publishsettings file. You can download yours here: https://windows.azure.com/download/publishprofile.aspx. Once you have your publishsettings file and a valid Azure VNet config, you can run the following script to run our newly created resource.
Configuration SetAzureVNet { Import-DscResource -Module cAzure Import-DscResource -Module xAzure xAzureSubscription MSDN { Ensure = "Present" AzureSubscriptionName = "Windows Azure MSDN - Visual Studio Premium" AzurePublishSettingsFile = "C:TempAzure.publishsettings" } cAzureVNetConfig VNet { DependsOn = "[xAzureSubscription]MSDN" ConfigurationPath = "C:Tempvnet.netcfg" } } SetAzureVNet -OutputPath C:TempDSC -Force Start-DscConfiguration -ComputerName 'localhost' -Path C:TempDSC -Wait -Verbose
Some explanation is in place. First the Configuration
keyword. The new keyword allows you to specify a PowerShell DSC configuration. This is a declarative way of specifying the desired state of a node.
Import-DscResource
looks like a PowerShell cmdlet but it actually isn't and it can only be used inside a configuration. We use Import-DscResource
to import both our new module and the xAzure
module from the DSC Resource Kit.
Next we use the xAzureSubscription
resource to initialize our Azure subscription. Note you need the name of your subscription and your publishsettings file. Finally we call upon our new resource to configure the Azure VNet. Note that our resource has a dependency on xAzureSubscription
, meaning that it should only be executed when that one has run.
That's it! When you run the script from PowerShell ISE, this is what you should get:
I didn't go into DSC details here but I've included enough references so that you can find this information on your own.