The case of the “Silly” DSC custom resource

The case of the “Silly” DSC custom resource

  • Comments 6
  • Likes

In recent customer conversations, we got feedback that documentation on writing a custom Windows PowerShell Desired State Configuration (DSC) resource is still something which can use improvement, especially in the areas of step-by-step examples. Therefore, I have decided to write up a “silly” custom DSC resource to get our readers quickly up and running with writing custom DSC resources. In the next blog posts, I will guide you through writing a real-life example for managing your servers, and what kind of challenges come with that.


Assumptions and primers

If you’re new to DSC, or just didn’t have the time to start yet and want to start reading, please revisit some blog posts we have released previously on DSC.

 

The anatomy of a DSC resource

When you explore a DSC resource more closely, you will find that it consists of 3 components:

  1. The DSC module - a PowerShell module file with the extension psm1
  2. The DSC data file - a PowerShell data file with the extension psd1
  3. The DSC schema file - a Managed Object Format file with extension MOF

We require these 3 components for our custom resource. Luckily, the Windows PowerShell team has provided us with the Resource Designer Tool, which enables you to generate the DSC module and schema files, and helps prevent you from making syntax errors. You don’t have to use the Resource Designer Tool, but it makes writing your first DSC resource a lot easier. After you play around a bit, you will figure out that you could also leverage a boilerplate approach, as mentioned by Michael Greene in this blog post. Effectively, its about reusing templates, which makes total sense if you’re writing multiple custom DSC resources.


The objective of our “silly” DSC resource

Fair warning:

When I start with a new technology I would like to understand better, I tend to keep things as simple as I can to focus on the concepts. So in this blog post I will walk you through the creation of a custom DSC resource. The sample resource won't be of any use to you other then to understand the authoring process better. There will always be multiple options to choose from if it comes to writing PowerShell scripts. So please consider this blog post as an conceptual example for writing a custom DSC resource.  For that reason, no error handling has been added, which in a production environment, should always be the case. In addition, I didn’t want to change any configuration on my machine to test-drive DSC.

In my “silly” example I simply want to check if a text file has the value of either Red, Blue or None. For that to happen we will leverage the DSC keywords Present and Absent. So if I use the the combination of Present and Red, DSC makes sure that the value of Red is set. If I use the combination of Absent and Red, DSC makes sure that the value is not Red. That leads me to think about what kind of parameters I want to use in my example. So far, I’ve come up with the color I need to validate, so that’s 1. Oh, and of course, I need the path and file name to check, so that makes 2. Let’s call those parameters $Color and $ColorFilePath, and start with those.

Time to download and install the Resource Designer Tool from here. Unzip the files under the $env:ProgramFiles\WindowsPowerShell\Modules folder. Make sure that the module is available by opening a Windows PowerShell window and run Get-Module –ListAvailable to verify that xDscResourceDesigner is listed.

image

Now that we have that in place, we can start authoring our “silly” custom DSC resource.


The skeleton of our “silly” custom DSC resource

The previously-mentioned blog post already talks about the Resource Designer Tool in depth, so no need to repeat that content here. Let’s start with writing up our definition by starting a Windows PowerShell ISE session, and start to define our custom resource:

001
002
003
004
005
006
007
008
009
Import-Module xDSCResourceDesigner

# Define my DSC parameters
$Color = New-xDscResourceProperty -Name Color -Type String -Attribute Key -ValidateSet "Red", "Blue", "None" 
$Ensure = New-xDscResourceProperty -Name Ensure -Type String -Attribute Write -ValidateSet "Present", "Absent" 
$ColorFilePath = New-xDscResourceProperty -Name ColorFilePath -Type String -Attribute Write

# Create the DSC resource
New-xDscResource -Name "SillyDSCresource" -Property $Color, $Ensure, $ColorFilePath -Path "C:\Program Files\WindowsPowerShell\Modules\SillyDSCmodule" -ClassVersion 1.0 -FriendlyName "SillyDSCresource" -Force

I first need to import the DSC Resource Designer module. Then I need to define my parameters. In addition to my Color and ColorFilePath parameter, I also need to define the Ensure parameter. This takes care of the Present and Absent keywords I’ve mentioned earlier. Notice that I’m using ValidateSet to constrain my values, but besides that, I’m using normal attributes like Type to define my parameter types

On line 009, I invoke the creation of my new DSC resource, and specify its name, path and friendly name. Make sure that your Property values match your variables, or this will throw an error. This is likely to happen when you forget a parameter, and try to add it without using Update-DscResource, but decided to generate your DSC resource all over.

At this point, you could also update your MOF file directly in any editor if you know what to edit. If you do so, make sure that you validate it by running Test-DscSchema which returns a true  - we’re all good - or false - you probably made a syntax error somewhere. Now that we have our DSC schema in place, it's time to examine the files that were created by our New-xDscResource command. In your specified path, you will find a new DSC module and MOF file:

image

If we open our new DSC module in Windows PowerShell ISE, you will find our parameters, and the three core DSC TargetResource functions:

  • Get – this will get whatever we need to validate
  • Set – this will set whatever we need to set
  • Test – this will validate whatever it is we need to validate and will return a true or false state

The DSC data file

A DSC resource depends on the existence of a DSC data file, a Windows PowerShell psd1 file. The fastest and easiest way to create one is to copy an existing one and modify it. Since you’ve already installed the xDSCResourceDesigner module, you have a DSC data file which you can use.

image

Copy the xDSCResourceDesigner.psd1 file to your custom DSC module folder, and rename it to match your module name. So in my example, I’ve copied and renamed it to SillyDSCresource.psd1. Make sure that you locate it directly under your module folder at the same level as the DSCResources folder.

Make sure that you modify the values as highlighted in the example above. To generate a unique GUID, open up a new Windows PowerShell window, and run the command  [guid]::newguid() to generate a new GUID:

image


Authoring our “silly” DSC module

Now let’s start modifying our silly DSC module file, and open the module (psm1) file. We will work on the Get-TargetResource function first to make our param section more complete; we'll also configure our returnValue parameter:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
#region GET Settings
function Get-TargetResource
{
    [CmdletBinding()
]
    [
OutputType([System.Collections.Hashtable])]

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

        [Parameter(Mandatory)]
        [ValidateSet("Red", "Blue", "None")]
        [System.String]$Color,

        [Parameter(Mandatory)]
        [System.String]$ColorFilePath

   ) 


    $returnValue = @{
        Color = $Color
        Ensure = $Ensure
        }
   
    $returnValue
}

#endregion

Now we have that in place, we can start working on the most interesting part: the make it so section under Set-TargetResource. Here is where we set the desired value (if required). Since this is all PowerShell, we can even write to a log file to log our actions or do other things:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
#region SET Settings
function Set-TargetResource
{
       [CmdletBinding()
]
       [
OutputType([System.Collections.Hashtable])]

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

        [Parameter(Mandatory)]
        [ValidateSet("Red", "Blue", "None")]
        [System.String]$Color,

        [Parameter(Mandatory)]
        [System.String]$ColorFilePath

    )

   
    $ColorToEvaluate = (Get-Content $ColorFilePath)
   
    #In case the configuration is set to Present, the value of $ColorToEvaluate needs to be equal to $Color, if it's not, we make it so
    if (($Ensure -eq "Present") -and ($ColorToEvaluate -ne $Color))
    {
        #write to log file for debugging
        Add-Content c:\log\silly_log.txt "Not compliant - Present defined, detected value is: $ColorToEvaluate, setting value to $Color"    
       
        #Set the correct value and write to file
        Out-File C:\log\color.txt -inputobject $Color
   
    }
    #If the configration is set to Absent and IF the colors match, we will set it to "None"
    elseif (($Ensure -eq "Absent") -and ($ColorToEvaluate -eq $Color))
    {

       Add-Content c:\log\silly_log.txt "Not compliant, Absent defined, detected non-compliant value is: $ColorToEvaluate, setting value to None"
       Out-File C:\log\color.txt -inputobject "None"
    }

   
}
#endregion

 

Finally, there's the Test-TargetResource section, where we validate our configuration, which returns true or false. This is also the place where you can add Write-Verbose commands to display output to the user, which shows up when you invoke the Start-DscConfiguration command:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
#region TEST Settings
function Test-TargetResource
{
        [CmdletBinding()
]
        [
OutputType([System.Collections.Hashtable])]

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

        [Parameter(Mandatory)]
        [ValidateSet("Red", "Blue", "None")]
        [System.String]$Color,

        [Parameter(Mandatory)]
        [System.String]$ColorFilePath

)

   
    $ColorToEvaluate = (Get-Content $ColorFilePath)
   
    $bool = $false
    if (($Ensure -eq "Present") -and ($ColorToEvaluate -eq $Color))
    {
        #Verbose output will be shown if the Start-DscConfiguration is being invoked
        Write-Verbose "Compliant: Present defined, value should be $Color, detected value is: $ColorToEvaluate"
        $bool = $true
   
    }
    elseif (($Ensure -eq "Present") -and ($ColorToEvaluate -ne $Color))
    {
        Write-Verbose "Non-Compliant: Present defined, value should be $Color, detected value: $ColorToEvaluate, making it so"
        $bool = $false
    }
    elseif (($Ensure -eq "Absent") -and ($ColorToEvaluate -ne $Color))
    {
        Write-Verbose "Compliant: Absent defined, value should NOT be $Color, detected value: $ColorToEvaluate"
        $bool = $true
    }
    elseif (($Ensure -eq "Absent") -and ($ColorToEvaluate -eq $Color))
    {
        Write-Verbose "Non-Compliant: Absent defined, value should NOT be $Color, detected value: $ColorToEvaluate, correcting..."
        $bool = $false
    }
           
    $bool


}
#endregion


Export-ModuleMember -Function 
*-TargetResource

 

Now that we have configured our three core DSC functions, we can save it to our module folder, and create a configuration to be deployed.


Creating our “silly” DSC configuration

With our Get, Set and Test DSC functions in place, it should be really straightforward to create a configuration:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
param ($MachineName="localhost")
Configuration SillyTest

{
    Import-DscResource -ModuleName SillyDSCmodule
   

    node $MachineName
    {
         SillyDSCresource MySillyTest
         {
         Ensure        = 'Present'
         Color         = 'Red'
         ColorFilePath = 'c:\DSC\color.txt'

         }

    }
}

$MOFpath = "c:\DSC\MOF"
SillyTest -MachineName "localhost" -OutputPath $MOFpath

Start-DscConfiguration -ComputerName 'localhost' -wait -force -verbose -path $MOFpath

Before we apply our DSC configuration, let’s make sure that we have a text file with a value in it, stored in our ColorFilePath:

image


Applying and testing our DSC configuration

When we run our DSC script, and have invoked Start-DscConfiguration, we can see our configuration being applied nicely:

image

Did you notice the verbose output as defined in the Test-TargetResource?

And we even have our log file as configured under the Set-TargetResource section, nice!

image

When we invoke Test-DscConfiguration, we should be good; let’s check if the value actually changed:

image

Nice!


Test driving configuration drift

Let’s see what happens if we change our configuration, we set Ensure to Absent, and we invoke Start-DscConfiguration:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
param ($MachineName="localhost")
Configuration SillyTest

{
    Import-DscResource -ModuleName SillyDSCmodule
   

    node $MachineName
    {
         SillyDSCresource MySillyTest
         {
         Ensure        = 'Absent'
         Color         = 'Red'
         ColorFilePath = 'c:\DSC\color.txt'

         }

    }
}

$MOFpath = "c:\DSC\MOF"
SillyTest -MachineName "localhost" -OutputPath $MOFpath

Start-DscConfiguration -ComputerName 'localhost' -wait -force -verbose -path $MOFpath

Remember that the value of our c:\DSC\color.txt file is still Red.

image

Ah…nice!

Let’s check our C:\DSC\color.txt value:

image

If you are surprised at the None outcome, revisit the Set-TargetResource section Smile (hint: look at line 035).

If we now change the value of C:\DSC\color.txt to Red, and run Test-DscConfiguration, we should see false as our output:

image

If we then run Start-DscConfiguration, this is nicely corrected again.


Wrapping up

I hope you found this “silly” DSC example useful. Stay tuned for a real-life example in the next blog post!

To wrap up, I’m including the full module here below. Until next time, happy automating!


001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# Silly Custom DSC Resource
# This is a silly custom DSC Resource for testing purposes only
# It checks the content of a dummy text file, which should either contain "Red", "Blue" or "None"

#region GET Settings

function Get-TargetResource
{
[CmdletBinding()
]
[
OutputType([System.Collections.Hashtable])]

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

        [Parameter(Mandatory)]
        [ValidateSet("Red", "Blue", "None")]
        [System.String]$Color,

        [Parameter(Mandatory)]
        [System.String]$ColorFilePath

    ) 

  
    $returnValue = @{
        Color = $Color
        Ensure = $Ensure
        }
   
    $returnValue
}

#endregion

#region SET Settings

function Set-TargetResource
{
[CmdletBinding()
]
[
OutputType([System.Collections.Hashtable])]

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

        [Parameter(Mandatory)]
        [ValidateSet("Red", "Blue", "None")]
        [System.String]$Color,

        [Parameter(Mandatory)]
        [System.String]$ColorFilePath

    )

   
    $ColorToEvaluate = (Get-Content $ColorFilePath)
   
    #In case the configuration is set to Present, the value of $ColorToEvaluate needs to be equal to $Color, if it's not, we make it so
    if (($Ensure -eq "Present") -and ($ColorToEvaluate -ne $Color))
    {
        #write to log file for debugging
        Add-Content c:\log\silly_log.txt "Not compliant - Present defined, detected value is: $ColorToEvaluate, setting value to $Color"    
       
        #Set the correct value and write to file
        Out-File C:\DSC\color.txt -inputobject $Color
   
    }
    #If the configration is set to Absent and IF the colors match, we will set it to "None"
    elseif (($Ensure -eq "Absent") -and ($ColorToEvaluate -eq $Color))
    {

       Add-Content c:\log\silly_log.txt "Not compliant, Absent defined, detected non-compliant value is: $ColorToEvaluate, setting value to None"
       Out-File C:\DSC\color.txt -inputobject "None"
    }

   
}
#endregion

#region TEST Settings

function Test-TargetResource
{
     [CmdletBinding()
]
     [
OutputType([System.Collections.Hashtable])]

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

        [Parameter(Mandatory)]
        [ValidateSet("Red", "Blue", "None")]
        [System.String]$Color,

        [Parameter(Mandatory)]
        [System.String]$ColorFilePath

    )

   
    $ColorToEvaluate = (Get-Content $ColorFilePath)
   
    $bool = $false
    if (($Ensure -eq "Present") -and ($ColorToEvaluate -eq $Color))
    {
        #Verbose output will be shown if the Start-DscConfiguration is being invoked
        Write-Verbose "Compliant: Present defined, value should be $Color, detected value is: $ColorToEvaluate"
        $bool = $true
   
    }
    elseif (($Ensure -eq "Present") -and ($ColorToEvaluate -ne $Color))
    {
        Write-Verbose "Non-Compliant: Present defined, value should be $Color, detected value: $ColorToEvaluate, making it so"
        $bool = $false
    }
    elseif (($Ensure -eq "Absent") -and ($ColorToEvaluate -ne $Color))
    {
        Write-Verbose "Compliant: Absent defined, value should NOT be $Color, detected value: $ColorToEvaluate"
        $bool = $true
    }
    elseif (($Ensure -eq "Absent") -and ($ColorToEvaluate -eq $Color))
    {
        Write-Verbose "Non-Compliant: Absent defined, value should NOT be $Color, detected value: $ColorToEvaluate, correcting..."
        $bool = $false
    }
           
    $bool


}
#endregion


Export-ModuleMember -Function 
*-TargetResource


Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • What I really dont understand is that Microsoft stuff writes good documentation, without integration in the real documentation. The real documentation inside of Technet os MSDN is left behind with a very low Quality.
    Even the PowerShell Team Blog writes tons of DSC stuff without touching the real DSC documentation. Does Microsoft do not work with Microsoft?

    greets Peter Kriegel
    founder member of the german speaking Windows PowerShell Community
    HTTP://www.PowerShell-Group.eu

  • Great basic example of DSC that has been missing in all the docs and blog examples so far.

    One gotcha I have discovered : caching. I would like to see an example of using LocalConfigurationManager inside the Configuration block including the DebugMode = $true, and an explanation of the caching of the resources as a developer .is editing resources.

    Another example blog could be using this "silly" example leveraging a very simple pull server, both with a certificate and without it in a test environment.

    Dusty

  • Hello Peter, we have been taking valuable feedback - like yours - on documentation very seriously and have been improving since then. We need to do better though. It is especially on new technologies emerging and being adopted that we have this Building Clouds blog for to accelerate those documentation improvements. Thanks, Tiander.

  • Thanks for your suggestions Dusty. We will certainly take those into consideration for our next blog posts to follow. On your ask on a pull server example, please stay tuned...

  • Any reason you don't use the New-ModuleManifest cmdlet instead of manually copy and pasting a psd1 from a current resource? Seems problematic if the resource you copy from includes custom information and/or config.

  • Hi Tiander,

    I wonder if you could possibly help me with something that I spent a lot of time on.
    My question is regarding changing something in a custom resource, and "reloading" it.
    You will see, that changes to your custom resource are not being automatically picked up. (WMF 4.0).

    As an example you may create a silly resource that returns a hello message, and dsc configuration that uses that resource. After pushing the configuration, which works fine, now change something in that silly resource - let's change the message that it returns. (or anything you want)

    Calling Get-DscConfiguration wouldn't picked up this change. Start-DscConfiguration again wouldn't have any effect as well. It still uses that old resource before change.

    The only way for dsc to see the changed resource is to kill the process right after executing any DSC command. Rerunning command would then pick new changes. (I have little ps code snippet that locates correct process)

    However it's not an option, if I am pushing such custom resource to 100 of nodes. I know, that eventually it would be maintenance nightmare, especially that I will have a lot of custom resources.

    Now, I am aware that WMF 5.0 has special flag DEBUG for Local Configuration Manager, which is supposedly make it so dsc always loads new resource. This is not solution for me, as at my company we have at this moment WMF 4.0.

    I also believe that this "cache" is persisted somehow, as this dsc process seems to shutdown after a while, and comes back if I run again some DSC command still with old resource modules.

    Import-DscResource does not have -Force flag.
    I really don't know how to solve this little thing - is there something obvious I haven't found.

    I would really appreciate some help.
    Adam