This post marks my first adventure with PowerShell Desired State Configuration. Over the past few weeks, there have been a few announcements around Microsoft Azure. Once of these, is an extended version of the Azure Resource Manager which brings us the ability to run PowerShell DSC on Virtual Machines. This update changes everything and I will delve into it in an upcoming post.
DSC Module to Prepare a Stripe Volume
Using the xDSCResourceDesigner enabled by installing Windows Management Framework 5.0 Preview April 2015, I was able to use the following commands to scaffold a Customer PowerShell Desired State (DSC) Module that allows me to stripe data disks on an Azure Virtual Machine.
Install the xDSCResourceDesigner Module
Install-Module -Name xDSCResourceDesigner
scaffold a Customer PowerShell Desired State (DSC) Module
New-xDscResource –Name cStripeVHDsAndPrepareVolume ` -Property (New-xDscResourceProperty –Name DriveSize ` –Type UInt64 ` -Attribute Key), ` (New-xDscResourceProperty –Name NumberOfColumns ` -Type Uint32 ` -Attribute Required) ` -Path 'C:\Program Files\WindowsPowerShell\Modules\' ` -ModuleName cDiskTools ` -FriendlyName cDiskTools ` -Verbose
This will create many files in
C:\Program Files\WindowsPowerShell\Modules\cDiskTools
The file that interests us the most because this is where the logic is implemented.
cDiskTools\DSCResources\StripeVHDsAndPrepareVolume\StripeVHDsAndPrepareVolume.psm1
Implementing the Custom DSC Module
Get-TargetResource – This function will always return a hastable. It’ll gather information about the task we wanted to know, and return them in a hashtable.
function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $true)] [System.UInt64] $DriveSize, [parameter(Mandatory = $true)] [System.UInt32] $NumberOfColumns ) Try { if (Test-Path $DriveLetter) { Write-Debug 'F:/ exists on target.' @{ DriveLetter = 'F' Mounted = $true } } else { Write-Debug "F:/ can't be found." @{ DriveLetter = 'F' Mounted = $false } } } Catch { throw "An error occured getting the F:/ drive informations. Error: $($_.Exception.Message)" } }
Set-TargetResource – This function will apply what we want to do. In this case, it will create a Storage Space with all the disks that can be pooled. It will create a Virtual Disk and Initialize it. Then it will create a partition and assign a Drive Letter. Finally, it will Format the Volume.
function Set-TargetResource { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [System.UInt64] $DriveSize, [parameter(Mandatory = $true)] [System.UInt32] $NumberOfColumns ) Write-Verbose 'Creating Storage Pool' New-StoragePool -FriendlyName 'LUN-0' ` -StorageSubSystemUniqueId (Get-StorageSubSystem -FriendlyName '*Space*').uniqueID ` -PhysicalDisks (Get-PhysicalDisk -CanPool $true) Write-Verbose 'Creating Virtual Disk' New-VirtualDisk -FriendlyName 'Datastore01' ` -StoragePoolFriendlyName 'LUN-0' ` -Size $DriveSize ` -NumberOfColumns $NumberOfColumns ` -ProvisioningType Thin ` -ResiliencySettingName Simple Start-Sleep -Seconds 20 Write-Verbose 'Initializing Disk' Initialize-Disk -VirtualDisk (Get-VirtualDisk -FriendlyName 'Datastore01') Start-Sleep -Seconds 20 $diskNumber = ((Get-VirtualDisk -FriendlyName 'Datastore01' | Get-Disk).Number) Write-Verbose 'Creating Partition' New-Partition -DiskNumber $diskNumber ` -UseMaximumSize ` -AssignDriveLetter ` -DriveLetter F Start-Sleep -Seconds 20 Write-Verbose 'Formatting Volume and Assigning Drive Letter' Format-Volume -DriveLetter F ` -FileSystem NTFS ` -NewFileSystemLabel 'Data' ` -Confirm:$false ` -Force }
Test-TargetResource – This function will test the system so see if the set-targetresource should be applied or not. It’ll always return a boolean.
function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [System.UInt64] $DriveSize, [parameter(Mandatory = $true)] [System.UInt32] $NumberOfColumns ) $result = [System.Boolean] Try { if (Test-Path F) { Write-Verbose 'F:/ exists on target.' $result = $true } else { Write-Verbose "F:/ can't be found." $result = $false } } Catch { throw "An error occured getting the F:/ drive informations. Error: $($_.Exception.Message)" } $result }
Providing Metadata About The Module
In order to be able to import a Module, it must provide metadata about itself, through a manifest file whose extension is psd1. In our case, it can be found at the root of our Module folder hierarchy.
C:\Program Files\WindowsPowerShell\Modules\DiskTools\DiskTools.psd1
If you have multiple resource in your module, you will need a new psd1 in each resource! And a root module should exists in the module manifest.
Now, we need to verify that the module is found by PowerShell
get-module -ListAvailable | ? Name -like "cD*" ModuleType Version Name ---------- ------- ----------- Manifest 1.0 cDiskTools
Great, things seem to be in order. It’s time to write the DSC Configuration
configuration DataDisk { param ( [UInt64]$DriveSize = 32TB, [UInt32]$NumberOfColumns = 32 ) Import-DscResource -module cDiskTools node localhost { cDiskTools StripeVHDsAndPrepareVolume { DriveSize = $DriveSize NumberOfColumns = $NumberOfColumns } } } DataDisk
Testing The Module
This step can be a bit tricky especially in a scenario where we need disks to sacrifice. In other words, I need a temporary need a Monster Virtual Machine to test this out! Fortunately I have a script to create VM on Microsoft Azure with 32 data disks.
Using the script from my previous post, I create a brand new monster. To test this new DSC Module, I logged into the VM using Remote Desktop (RDP). Then I fired up Windows PowerShell and installed the DSC Service.
Add-WindowsFeature Dsc-Service
Copied my module to
C:\Program Files\WindowsPowerShell\Modules
And verified that it was found by PowerShell
get-module -ListAvailable | ? Name -like "cD*" ModuleType Version Name ---------- ------- ----------- Manifest 1.0 cDiskTools
Then I copied the DataDisk.ps1 configuration file to
C:\Configuration\
Executing DataDisk.ps1 produces a file (contents shown below) named localhost.mof. In this scenario, the file is used to apply the desired configuration to the localhost.
/* @TargetNode='localhost' @GeneratedBy=brisebois @GenerationDate=05/08/2015 05:37:20 @GenerationHost=MSBRISEBOIS */ instance of cStripeVHDsAndPrepareVolume as $cStripeVHDsAndPrepareVolume1ref { ResourceID = "[cDiskTools]StripeVHDsAndPrepareVolume"; NumberOfColumns = 32; SourceInfo = "C:\\Configuration\\DataDisk.ps1::10::9::cDiskTools"; DriveSize = 35184372088832; ModuleName = "cDiskTools"; ModuleVersion = "1.0"; }; instance of OMI_ConfigurationDocument { Version="1.0.0"; Author="brisebois"; GenerationDate="05/08/2015 05:37:20"; GenerationHost="MSBRISEBOIS"; };
Now it’s time to test drive our Custom DSC
Start-DscConfiguration -Path C:\Configuration\DataDisk ` -ComputerName localhost ` -Force ` -wait ` -Verbose ` -Debug
If everything goes well we should get output that looks like the following
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfiguration Manager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguration'. VERBOSE: An LCM method call arrived from computer MSBRISEBOIS with user sid S-1-5-21-2374999437-3767087714-2116828215-500. VERBOSE: [MSBRISEBOIS]: LCM: [ Start Set ] VERBOSE: [MSBRISEBOIS]: LCM: [ Start Resource ] [[cDiskTools]StripeVHDsAndPrepareVolume] VERBOSE: [MSBRISEBOIS]: LCM: [ Start Test ] [[cDiskTools]StripeVHDsAndPrepareVolume] VERBOSE: [MSBRISEBOIS]: [[cDiskTools]StripeVHDsAndPrepareVolume] F:/ can't be found. VERBOSE: [MSBRISEBOIS]: LCM: [ End Test ] [[cDiskTools]StripeVHDsAndPrepareVolume] in 0.2500 seconds. VERBOSE: [MSBRISEBOIS]: LCM: [ Start Set ] [[cDiskTools]StripeVHDsAndPrepareVolume] VERBOSE: [MSBRISEBOIS]: [[cDiskTools]StripeVHDsAndPrepareVolume] Creating Storage Pool VERBOSE: [MSBRISEBOIS]: [[cDiskTools]StripeVHDsAndPrepareVolume] Creating Virtual Disk VERBOSE: [MSBRISEBOIS]: [[cDiskTools]StripeVHDsAndPrepareVolume] Initializing Disk VERBOSE: [MSBRISEBOIS]: [[cDiskTools]StripeVHDsAndPrepareVolume] Creating Partition VERBOSE: [MSBRISEBOIS]: [[cDiskTools]StripeVHDsAndPrepareVolume] Formatting Volume and Assigning Drive Letter VERBOSE: [MSBRISEBOIS]: LCM: [ End Set ] [[cDiskTools]StripeVHDsAndPrepareVolume] in 210.0800 seconds. VERBOSE: [MSBRISEBOIS]: LCM: [ End Resource ] [[cDiskTools]StripeVHDsAndPrepareVolume] VERBOSE: [MSBRISEBOIS]: LCM: [ End Set ] in 226.3035 seconds. VERBOSE: Operation 'Invoke CimMethod' complete. VERBOSE: Time taken for configuration job to complete is 210.862 seconds
In Closing
Being a developer at heart, I never really took the time to play with PowerShell DSC. As Azure evolves, I’m pulled in a bunch of new directions and this is definitely one that I will continue to explore. Azure and Automation go hand in hand. It’s important to be capable to create environments in a repeatable and predictable manner. This is exactly what the Azure Resource Manager (ARM) and PowerShell DSC brings to the table.
This is a game changer for many ongoing projects who struggle with managing environment configurations across regions. Take some time and start digging into PowerShell DSC, your Infrastructure as a Service (IaaS) projects will definitely benefit from it.
Hi Alexandre,
Just stumbled across this post and was really impressed. I tried it out and got it to work no problem .Excellent write up!
The one thing I was wondering is if there’d be a way to adapt this to scan for the number of available disks (instead of going for 32) and the nassigning all of the available space to the new volume.
When using flexible JSON templates with dynamic datadisks (like in https://github.com/Azure/azure-quickstart-templates/tree/master/201-vm-dynamic-data-disks-selection) it’s not always clear in advance how many disks there will be.
Cheers
Sven
LikeLike
Hi all. I have written full-size DSC-module for that purpose. Check it out, it might be useful. https://github.com/CursedbyFlame/cStoragePool
LikeLike