Thursday, 23 November 2017

Learn PowerShell DSC - Part 7

Introduction

In this part, we’ll go ahead and create our own DSC Resource. Why are we doing this? Well, when you start using DSC, you’ll find that you need to do something but cannot find a DSC Resource for it.

Other parts in this series:

Create a new DSC Resource

We’re going to create a simple resource which ensures a folder is present or absent. There’s already a DSC Resource for this but we’re more focussed on the process of creating a DSC resource. An overview of the steps is below:

  1. Install the DSC Resource Designer Module
  2. Create the DSC Resource properties
  3. Create the DSC Resource files
  4. Create the module manifest
  5. Add code to the DSC Resource
  6. Copy the module to the PowerShell module directory
  7. Test the DSC Resource

Let’s get to it - follow the steps below.

1) Install the DSC Resource Designer module

This module includes the cmdlets we’ll use to create a new DSC resource. These cmdlets include:

  • Import-xDscSchema
  • New-xDscResource
  • New-xDscResourceProperty
  • Test-xDscResource
  • Test-xDscSchema
  • Update-xDscResource

Install it by running the command below:

Install-Module xDscResourceDesigner -Force

2) Create the DSC Resource properties

These DSC Resource properties relate to what can be set when using the DSC Resource as part of a configuration. Our DSC Resource will be quite simple so in this case, we only need two properties:

  • Path - this is the path to the folder we will ensure is absent or present
  • Ensure - this is what we will do to the folder. It will only be able to be set to Present or Absent.

$Path=New-xDscResourceProperty -Name Path -Type string -Attribute Key
$Ensure=New-xDscResourceProperty -Name Ensure -Type string -Attribute Write -ValidateSet Present, Absent

3) Create the DSC Resource files

We’ll use the New-xDscResource cmdlet to create the file and folder structure for our new DSC Resource which we’ll call DemoFolder. We’ll call our new module DemoModule so we’ll be saving our new DSC Resource in a DemoModule folder:

New-xDscResource -Name DemoFolder -Property $Path, $Ensure -Path 'C:\scripts\DemoModule'

This creates a number of files and folder structure in C:\Scripts. We’ll go through this later on.

4) Create the module manifest

As when creating a standard PowerShell module, we need to create a module manifest which includes information about the module. If you want more information on how to create a PowerShell module, see here.

Let’s create our module manifest. We’re going to set a few options here:

  • Author: MG
  • CompanyName: MG Enterprises
  • Description: Demo Module

New-ModuleManifest `
-Path 'C:\Scripts\DemoModule\DemoModule.psd1' `
-Guid ([GUID]::NewGuid()) `
-ModuleVersion 1.0 `
-Author MG `
-CompanyName 'MG Enterprises' `
-Description 'Demo Module'

5) Add code to the DSC Resource

We should now have the below folder structure and files:

image

  • DemoModule.psd1 - this is the module manifest we created in step 4
  • DemoFolder.psm1 - this is the PowerShell module file which will hold our DSC Resource code
  • DemoFolder.schema.mof - this is the MOF schema which contains information about the DSC Resource

When we look in the .psm1 file for a particular DSC Resource we've created, we'll see that the DSC resource designer created three empty functions with some parameters which match the properties you created with New-xDscResourceProperty. These are:

  • Test-TargetResource - This function checks if the target machine configuration matches the DSC configuration. The function needs to provide a Boolean output (true or false) and it should be fast and lightweight as it will potentially run regularly.
  • Set-TargetResource - This is what performs the configuration change if the Test-TargetResource comes back false - i.e. in our case it either creates or deletes the specified folder.
  • Get-TargetResource - This must return a hash table of the current configuration. It's not used when setting or checking the configuration. It's only used when we use Get-DscConfiguration i.e. when troubleshooting.

Our 'empty' DemoFolder.psm1 file is below:

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

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    <# $returnValue = @{ Path = [System.String] Ensure = [System.String] } $returnValue #>
}


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

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

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    #Include this line if the resource requires a system reboot.
    #$global:DSCMachineStatus = 1

}


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

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

    #Write-Verbose "Use this cmdlet to deliver information about command processing."

    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    <# $result = [System.Boolean] $result #>
}

Export-ModuleMember -Function *-TargetResource


Let’s add some code to our DemoFolder.psm1 file. I’m not going to go through each line of code as this will really take some time and this post is all about creating the DSC resource.

Below you can see the completed file where I’ve written out the code.


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

    Write-Verbose "Checking if folder is present" 

    if(Get-Item $Path -ErrorAction SilentlyContinue -ErrorVariable a)
        {
            $Ensure="Present”
            if($a.Exception){Write-Debug $a.Exception} 
        }
    else
        {
            $Ensure="Absent”
            if($a.Exception){Write-Debug $a.Exception}
        }

    # Create a hashtaable which has path, present and ensure
    $returnValue = @{
    Path = [System.String]$Path
    Ensure = [System.String]$Ensure
    }

    # Output the hashtable - this is used for troubleshooting
    $returnValue
}

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

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

    # If ensure is true, create the folder and if false then delete it and its contents
    if($Ensure -eq 'Present')
        {
            Write-Verbose "Creating the folder"
            New-Item -ItemType Directory -Path $Path -ErrorAction SilentlyContinue -ErrorVariable a | Out-Null
            if($a.Exception){Write-Debug $a.Exception}

        }
    else
        {
            Write-Verbose "Removing the folder"
            Remove-Item $Path -Recurse -Force -ErrorAction SilentlyContinue -ErrorVariable a
            if($a.Exception){Write-Debug $a.Exception}
        }
   
    #Include this line if the resource requires a system reboot.
    #$global:DSCMachineStatus = 1

}

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

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

    # If ensure is true and the folder exists, return true. If folder doesn't exist then return false.
    # If ensure is false and folder exists then return false. If folder doens't exist then return true.
    if($Ensure -eq 'Present')
        {
            Write-Verbose "Checking if the folder exists"
            if(Get-Item $Path -ErrorAction SilentlyContinue -ErrorVariable a)
                {
                    $Result = $true
                }
            else
                {
                    $Result = $false
                }
            if($a.Exception){Write-Debug $a.Exception}
        }
    else
        {
            Write-Verbose "Checking that the folder does not exist"
            if(Get-Item $Path -ErrorAction SilentlyContinue -ErrorVariable a)
                {
                    $Result = $false
                }
            else
                {
                    $Result = $true
                }
            if($a.Exception){Write-Debug $a.Exception}
        }
  
    # Write the boolean output - if false then DSC knows to then run the Set-DSCTargetResource function
    $result
}

Export-ModuleMember -Function *-TargetResource

6) Copy the module to the PowerShell module directory

We now need to copy our module directory over to the PowerShell module directory:

Copy-Item C:\scripts\DemoModule "C:\Program Files\WindowsPowerShell\Modules\DemoModule" -Recurse

We can now confirm that our new module and DSC Resource is visible to PowerShell:

Get-DscResource DemoFolder

image

7) Test the DSC Resource

So, we’re done creating our DSC Resource and are ready to create a DSC configuration to test it out.

Our simple test configuration is below - all it does is ensure that the C:\TestFolder is present on localhost. If you need a refresher on how to write configurations, then see part 1.

configuration DemoCreateFolder {
    param
        (
            [Parameter(Mandatory=$true)]
            [string[]]$ComputerName
        )      
Import-DscResource -ModuleName DemoModule

    Node $ComputerName
     {
            DemoFolder TestFolder
                {
                   Ensure = "Present"
                   Path = "C:\TestFolder"
                }
        }
}

We then create the DSC Configuration document (MOF file) as normal:

DemoCreateFolder -ComputerName localhost -OutputPath C:\DSC\Configs

Once done, let’s go ahead and push our DSC configuration:

Start-DscConfiguration -Path C:\DSC\Configs -Wait -Verbose

image

We can then confirm our target machine is in the desired state and that our test folder exists:

Test-DscConfiguration -Path C:\DSC\Configs
Get-Item C:\TestFolder

image

So, there we have it - we now know how to create DSC Resources. Next time we’ll go through composite resources and how we can save time.

Happy scripting!

No comments:

Post a Comment