DSC Resources
Desired State Configuration (DSC) Resources provide the building blocks for a DSC configuration.
A resource can model something as generic as a file or as specific as an IIS server setting. Groups of like resources are combined into a DSC Module. The module organizes the required files into a structure that is portable and includes metadata to identify how the resources are intended to be used.
In a DSC version 3, the the schema for a resource is defined as properties of a class in PowerShell. While the term "class" might seem intimidating at first, the implementation for DSC does not require advanced programming knowledge.
The resource is implemented by Get(), Set(), and Test() methods, equivalent to the
Get-TargetResource, Set-TargetResource, and Test-TargetResource functions in DSC version 2. As
a best practice, create PowerShell functions in the module that perform all the requirements
for the three methods, and keep the class section of the module as simple as possible.
Folder structure for a DSC resource
To implement a custom DSC resource, create the following folder structure. The class is defined in
MyDscResource.psm1 and the module manifest is defined in MyDscResource.psd1.
$env:ProgramFiles\WindowsPowerShell\Modules (folder)
|- MyDscResource (folder)
MyDscResource.psm1
MyDscResource.psd1
Create a DSCResource() PowerShell class
You use the class keyword to create a PowerShell class. To specify that a class is a DSC resource,
use the DscResource() attribute. The name of the class is the name of the DSC resource.
[DscResource()]
class File {
}
Declare properties
The DSC resource schema is defined as properties of the class. We declare three properties as follows.
[DscProperty(Key)]
[string] $path
[DscProperty(Mandatory)]
[Ensure] $ensure
[DscProperty()]
[string] $content
[DscProperty(NotConfigurable)]
[Reason[]] $Reasons
Notice that the properties are modified by attributes. The meaning of the attributes is as follows:
DscProperty(Key): The property is required. The property is a key. The values of all properties marked as keys must combine to uniquely identify a resource instance within a configuration.DscProperty(Mandatory): The property is required.DscProperty(NotConfigurable): The property is read-only. Properties marked with this attribute cannot be set by a configuration, but are populated by theGet()method when present.DscProperty(): The property is configurable, but it is not required.
The $Path and $SourcePath properties are both strings. The $CreationTime is a
DateTime property. The $Ensure property is an enumeration type,
defined as follows.
enum Ensure
{
Absent
Present
}
Embedding classes
To include a new type with defined properties that you can use within your resource, create a class with property types as previously described. For example:
class Reason {
[DscProperty()]
[string] $Code
[DscProperty()]
[string] $Phrase
}
Public and Private functions
You can create PowerShell functions within the same module file and use them inside the methods of
your DSC class resource. The functions must be declared as public, however the script blocks within
those public functions can call functions that are private. To be declared as public, the function
must be listed in the FunctionsToExport property of the module manifest.
<#
Public Functions
#>
function Get-File {
param(
[Ensure]$ensure,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
$fileContent = [Reason]::new()
$fileContent.code = 'file:file:content'
$filePresent = [Reason]::new()
$filePresent.code = 'file:file:path'
$ensureReturn = 'Absent'
$fileExists = Test-path $path -ErrorAction SilentlyContinue
if ($true -eq $fileExists) {
$filePresent.phrase = "The file was expected to be: $ensure`nThe file exists at path: $path"
$existingFileContent = Get-Content $path -Raw
if ([string]::IsNullOrEmpty($existingFileContent)) {
$existingFileContent = ''
}
if ($false -eq ([string]::IsNullOrEmpty($content))) {
$content = $content | ConvertTo-SpecialChars
}
$fileContent.phrase = "The file was expected to contain: $content`nThe file contained: $existingFileContent"
if ($content -eq $existingFileContent) {
$ensureReturn = 'Present'
}
}
else {
$filePresent.phrase = "The file was expected to be: $ensure`nThe file does not exist at path: $path"
$path = 'file not found'
}
return @{
ensure = $ensureReturn
path = $path
content = $existingFileContent
Reasons = @($filePresent,$fileContent)
}
}
function Set-File {
param(
[Ensure]$ensure = "Present",
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
Remove-Item $path -Force -ErrorAction SilentlyContinue
if ($ensure -eq "Present") {
New-Item $path -ItemType File -Force
if ([ValidateNotNullOrEmpty()]$content) {
$content | ConvertTo-SpecialChars | Set-Content $path -NoNewline -Force
}
}
}
function Test-File {
param(
[Ensure]$ensure = "Present",
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
$test = $false
$get = Get-File @PSBoundParameters
if ($get.ensure -eq $ensure) {
$test = $true
}
return $test
}
<#
Private Functions
#>
function ConvertTo-SpecialChars {
param(
[parameter(Mandatory = $true,ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$string
)
$specialChars = @{
'`n' = "`n"
'\\n' = "`n"
'`r' = "`r"
'\\r' = "`r"
'`t' = "`t"
'\\t' = "`t"
}
foreach ($char in $specialChars.Keys) {
$string = $string -replace ($char,$specialChars[$char])
}
return $string
}
Implementing the methods
The Get(), Set(), and Test() methods are analogous to the Get-TargetResource,
Set-TargetResource, and Test-TargetResource functions in a script resource.
As a best practice, minimize the amount of code within the class implementation. Instead, move the majority of your code to public functions in the module, which can then be independently tested.
<#
This method is equivalent of the Get-TargetResource script function.
The implementation should use the keys to find appropriate
resources. This method returns an instance of this class with the
updated key properties.
#>
[File] Get() {
$get = Get-File -ensure $this.ensure -path $this.path -content $this.content
return $get
}
<#
This method is equivalent of the Set-TargetResource script function.
It sets the resource to the desired state.
#>
[void] Set() {
$set = Set-File -ensure $this.ensure -path $this.path -content $this.content
}
<#
This method is equivalent of the Test-TargetResource script
function. It should return True or False, showing whether the
resource is in a desired state.
#>
[bool] Test() {
$test = Test-File -ensure $this.ensure -path $this.path -content $this.content
return $test
}
The complete file
The complete class file follows.
enum Ensure {
Absent
Present
}
<#
This class is used within the DSC Resource to standardize how data
is returned about the compliance details of the machine.
#>
class Reason {
[DscProperty()]
[string] $Code
[DscProperty()]
[string] $Phrase
}
<#
Public Functions
#>
function Get-File {
param(
[Ensure]$ensure,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
$fileContent = [Reason]::new()
$fileContent.code = 'file:file:content'
$filePresent = [Reason]::new()
$filePresent.code = 'file:file:path'
$ensureReturn = 'Absent'
$fileExists = Test-path $path -ErrorAction SilentlyContinue
if ($true -eq $fileExists) {
$filePresent.phrase = "The file was expected to be: $ensure`nThe file exists at path: $path"
$existingFileContent = Get-Content $path -Raw
if ([string]::IsNullOrEmpty($existingFileContent)) {
$existingFileContent = ''
}
if ($false -eq ([string]::IsNullOrEmpty($content))) {
$content = $content | ConvertTo-SpecialChars
}
$fileContent.phrase = "The file was expected to contain: $content`nThe file contained: $existingFileContent"
if ($content -eq $existingFileContent) {
$ensureReturn = 'Present'
}
}
else {
$filePresent.phrase = "The file was expected to be: $ensure`nThe file does not exist at path: $path"
$path = 'file not found'
}
return @{
ensure = $ensureReturn
path = $path
content = $existingFileContent
Reasons = @($filePresent,$fileContent)
}
}
function Set-File {
param(
[Ensure]$ensure = "Present",
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
Remove-Item $path -Force -ErrorAction SilentlyContinue
if ($ensure -eq "Present") {
New-Item $path -ItemType File -Force
if ([ValidateNotNullOrEmpty()]$content) {
$content | ConvertTo-SpecialChars | Set-Content $path -NoNewline -Force
}
}
}
function Test-File {
param(
[Ensure]$ensure = "Present",
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]$path,
[String]$content
)
$test = $false
$get = Get-File @PSBoundParameters
if ($get.ensure -eq $ensure) {
$test = $true
}
return $test
}
<#
Private Functions
#>
function ConvertTo-SpecialChars {
param(
[parameter(Mandatory = $true,ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[string]$string
)
$specialChars = @{
'`n' = "`n"
'\\n' = "`n"
'`r' = "`r"
'\\r' = "`r"
'`t' = "`t"
'\\t' = "`t"
}
foreach ($char in $specialChars.Keys) {
$string = $string -replace ($char,$specialChars[$char])
}
return $string
}
<#
This resource manages the file in a specific path.
[DscResource()] indicates the class is a DSC resource
#>
[DscResource()]
class File {
<#
This property is the fully qualified path to the file that is
expected to be present or absent.
The [DscProperty(Key)] attribute indicates the property is a
key and its value uniquely identifies a resource instance.
Defining this attribute also means the property is required
and DSC will ensure a value is set before calling the resource.
A DSC resource must define at least one key property.
#>
[DscProperty(Key)]
[string] $path
<#
This property indicates if the settings should be present or absent
on the system. For present, the resource ensures the file pointed
to by $Path exists. For absent, it ensures the file point to by
$Path does not exist.
The [DscProperty(Mandatory)] attribute indicates the property is
required and DSC will guarantee it is set.
If Mandatory is not specified or if it is defined as
Mandatory=$false, the value is not guaranteed to be set when DSC
calls the resource. This is appropriate for optional properties.
#>
[DscProperty(Mandatory)]
[Ensure] $ensure
<#
This property is optional. When provided, the content of the file
will be overwridden by this value.
#>
[DscProperty()]
[string] $content
<#
This property reports the reasons the machine is or is not compliant.
[DscProperty(NotConfigurable)] attribute indicates the property is
not configurable in DSC configuration. Properties marked this way
are populated by the Get() method to report additional details
about the resource when it is present.
#>
[DscProperty(NotConfigurable)]
[Reason[]] $Reasons
<#
This method is equivalent of the Get-TargetResource script function.
The implementation should use the keys to find appropriate
resources. This method returns an instance of this class with the
updated key properties.
#>
[File] Get() {
$get = Get-File -ensure $this.ensure -path $this.path -content $this.content
return $get
}
<#
This method is equivalent of the Set-TargetResource script function.
It sets the resource to the desired state.
#>
[void] Set() {
$set = Set-File -ensure $this.ensure -path $this.path -content $this.content
}
<#
This method is equivalent of the Test-TargetResource script
function. It should return True or False, showing whether the
resource is in a desired state.
#>
[bool] Test() {
$test = Test-File -ensure $this.ensure -path $this.path -content $this.content
return $test
}
}
Create a manifest
To make a class-based resource available to the DSC engine, you must include a
DscResourcesToExport statement in the manifest file that instructs the module to export the
resource. Our manifest looks like this:
@{
# Script module or binary module file associated with this manifest.
RootModule = 'File.psm1'
# Version number of this module.
ModuleVersion = '1.0.0'
# ID used to uniquely identify this module
GUID = 'fad0d04e-65d9-4e87-aa17-39de1d008ee4'
# Author of this module
Author = 'Microsoft Corporation'
# Company or vendor of this module
CompanyName = 'Microsoft Corporation'
# Copyright statement for this module
Copyright = ''
# Description of the functionality provided by this module
Description = 'Create and set content of a file'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.0'
# Functions to export from this module
FunctionsToExport = @('Get-File','Set-File','Test-File')
# DSC resources to export from this module
DscResourcesToExport = @('File')
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @(Power Plan, Energy, Battery)
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
} # End of PSData hashtable
}
}
Test the resource
After saving the class and manifest files in the folder structure as described earlier, you can
create a configuration that uses the new resource. For information about how to run a DSC
configuration, see Getting started. The following
configuration checks to see whether the file /tmp/test.txt exists and if the contents match
the string provided by the property Content. If not, the entire file is written.
Configuration MyConfig
{
Import-DSCResource -module File
File testFile
{
Path = "/tmp/test.txt"
Content = "DSC Rocks!"
Ensure = "Present"
}
}
MyConfig
Supporting PsDscRunAsCredential
Note
PsDscRunAsCredential is supported in PowerShell 5.0 and later.
The PsDscRunAsCredential property can be used in a resource block to specify that the resource should be run under a specified set of credentials.
Require or disallow PsDscRunAsCredential for your resource
The DscResource() attribute takes an optional parameter RunAsCredential. This parameter takes
one of three values:
OptionalPsDscRunAsCredential is optional for configurations that call this resource. This is the default value.MandatoryPsDscRunAsCredential must be used for any configuration that calls this resource.NotSupportedConfigurations that call this resource cannot use PsDscRunAsCredential.DefaultSame asOptional.
For example, use the following attribute to specify that your custom resource does not support using PsDscRunAsCredential:
[DscResource(RunAsCredential=NotSupported)]
class FileResource {
}
Declaring multiple class resources in a module
A module can define multiple class-based DSC resources. You can create the folder structure in either of the following ways:
Define the first resource in the
<ModuleName>.psm1file and subsequent resources under the DSCResources folder.$env:ProgramFiles\WindowsPowerShell\Modules (folder) |- MyDscResource (folder) |- MyDscResource.psm1 MyDscResource.psd1 |- DSCResources |- SecondResource.psm1Define all resources under the DSCResources folder.
$env:ProgramFiles\WindowsPowerShell\Modules (folder) |- MyDscResource (folder) |- MyDscResource.psm1 MyDscResource.psd1 |- DSCResources |- FirstResource.psm1 SecondResource.psm1
Note
In these examples, add the .PSM1 files under the DSCResources to the NestedModules
key in your .PSD1 file.
See Also
Feedback
Skicka och visa feedback för