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:
- The DSC module - a PowerShell module file with the extension psm1
- The DSC data file - a PowerShell data file with the extension psd1
- 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 fromhere. 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.
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:
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.
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:
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:
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:
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!
When we invoke Test-DscConfiguration, we should be good; let’s check if the value actually changed:
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.
Ah…nice!
Let’s check our C:\DSC\color.txt value:
If you are surprised at the None outcome, revisit the Set-TargetResource section Image may be NSFW.
Clik here to view. (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:
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!
Image may be NSFW.
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
Clik here to view.