Writting custom DSC resource

During the European PowerShell summit, the hackaton was focused on 4 subjects, but only one direction:  Build a custom DSC resource for the community.

It was very intresting, and like after each IT Event, you come home with many many ideas… Your wife still doesn’t like it but… Anyway, if you weren’t at the summit:

  • First, it’s a mistake, you should really come next year 😉
  • Second, i will try to share the magic building your own DSC resource.

 

For this exemple, i will take a really easy challenge. So to make this possible, you need, PowerShell v5 (or newer) and an internet connexion.

This post will only talk about using MOF files resources, with the v5 preview, you can also use classes to build resource, which means that you don’t even need mof files now. But classes are kinda buggy for now, so let’s focus on the old way.

There is a module to help you building resources, his name is xDSCResourceDesigner, so let’s download it.

Find-Module xDSCResourceDesigner

 

Ok, we have found the module, now let’s install it !

Install-Module xDSCResourceDesigner

You will be prompted by a windows, because you we don’t have the -force switch with the cmdlet!

dsc2

Now, as we have our cmdlet available, the resource will provide the possibility to mount or unmout a VHD(x) file on a system. It’s not a very hard task, but it helps me really understand the meaning of the generated resource script. So let’s build the resource script, and folder directories.

First, here is the code used to generate the DSC resource

New-xDscResource –Name cMountVHD -Property (
    New-xDscResourceProperty –Name VHDPath –Type String –Attribute Key), (
    New-xDscResourceProperty –Name Ensure –Type String –Attribute Write –ValidateSet 'Absent', 'Present'
) -Path "C:\Program Files\WindowsPowerShell\Modules\cMountVHD" -Verbose

Let me explain what this cmdlet does.

The cmdlet will create a directories tree like this

dsc4

 

And as you see, a psm1 file and a schema.mof file has appeared.

the psm1 file is the most important, it store the 3 functions used by DSC when you call a resource:

  • 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.
  • Set-TargetResource – This function will apply what we want to do. For our example it’ll mount or unmount the VHDx file.
  • 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.

Ok, so now, let’s edit our psm1 file to add our stuff about mounting a VHD(x) !

function Get-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Collections.Hashtable])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VHDPath
	)

	Try {
        if (Test-Path $VHDPath) {
            Write-Debug "ImagePath exists on target."
            $MountedImg = Get-DiskImage -ImagePath $VHDPath
            if ($MountedImg.Attached -eq "True") {
                @{
                    ImagePath = $VHDPath
                    Mounted   = "true"
                } 
            }
            else {
                Write-Verbose "$VHDPath not mounted."
            }
            
        }
        else {
            throw "$VHDPath can't be found."
        }
    }
    Catch {
        throw "An error occured getting the mounted image drive informations. Error: $($_.Exception.Message)"
    }
}


function Set-TargetResource {
	[CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VHDPath,

		[ValidateSet("Present","Absent")]
		[System.String]
		$Ensure
	)

	Write-Verbose "(Un)Mounting $VHDPath file."
    Try {
        $status = (Get-DiskImage -ImagePath $VHDPath).Attached
        Switch($Ensure) {
            
            "Present" {
                if ($status -ne "False") {
                        Mount-DiskImage -ImagePath $VHDPath
                        Write-Verbose "$VHDPath mounted successfully."
                }
                else {
                        Write-Verbose "$VHDPath is already mounted."
                }
            }
            
            "Absent" {
                if ($status -eq "True") {
                    Dismount-VHD -Path $VHDPath
                    Write-Verbose "$VHDPath unmounted successfully."
                }
                else {
                    Write-Verbose "$VHDPath already unmounted."
                }
            }
        }
    }
    Catch {
        throw $_.Exception.Message
    }

}

function Test-TargetResource
{
	[CmdletBinding()]
	[OutputType([System.Boolean])]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$VHDPath,

		[ValidateSet("Present","Absent")]
		[System.String]
		$Ensure
	)

	Try {
        if (Test-Path $VHDPath) {
            Write-Debug "ImagePath exists on target."
            $MountedImg = Get-DiskImage -ImagePath $VHDPath
            switch($MountedImg.Attached) {
                
                "True" { 
                    Switch($Ensure) {
                        "Present" { $result = $true }
                        "Absent" { $result = $false }
                    }
  
                }

                "False" { 
                    switch($Ensure) {
                        "Present" { $result = $false }
                        "Absent" { $result = $true }
                    }
                }
            }
            
        }
        else {
            $false
        }
    }
    Catch {
        throw "An error occured getting the mounted image drive informations. Error: $($_.Exception.Message)"
    }
    $result
}

Export-ModuleMember -Function *-TargetResource

We have now our module ready to import ?

Ahhehhhm i don’t think so. We need to build a manifest file in order to enable the import-module cMountVHD possible !

A manifest file is a *.psd1 file named like the module, and stored in module root, in our case it’s “C:\Program Files\WindowsPowerShell\Modules\cMountVHD”.

The following lines are enough to have a correct manifest file.

@{
# Version number of this module.
ModuleVersion = '1.0'

# ID used to uniquely identify this module
GUID = '95f50c0f-f88e-4195-a042-a7bb524c286b'

# Author of this module
Author = 'Fabien Dibot'

# Description of the functionality provided by this module
Description = 'Module with DSC Resources for Mounting VHD files'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.0'

# Functions to export from this module
FunctionsToExport = '*'

# Cmdlets to export from this module
CmdletsToExport = '*'
}

Remember that 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 if you check at your module list you should see your cMountVHD appears

get-module -ListAvailable | ? Name -like "cM*"

sqc5

 

So, now in our DSC script we can import this resource !

configuration Lab { 
    param ( 
        [string[]]$NodeName = 'localhost', 
         
        [string]$VHDx = 'G:\VHD\Master2012R2.vhdx', 

        [ValidateSet("Present","Absent")] 
        [string]$Ensure = "Present"         
    ) 
 
    Import-DscResource -module cMountVHD
 
    Node $NodeName { 

        cMountVHD MountVHDDC {
            VHDPath    = $VHDx
            Ensure     = "Present"
        }

    } 
} 

Push-Location G:\Scripts
Lab

Let’s see if this is working smoothly !

Start-DscConfiguration -Path G:\Scripts\Lab -wait -Verbose

First execution:

dsc6

 

Second Exectuion, nothing happens, as we want.

dsc7

 

 

Third Execution, if we put the Ensure value to Absent, let’s see if our test is OK, and the VHDx correctly unmounted !

dsc8Seems it’s working 🙂

Another tips when building your resource, if you want to shate it with community, which is far better, you should prefix the name by c, it means Community released !

I hope this post will bring you to the DSC darkside.