At the heart of Windows PowerShell Desired State Configuration are the resources. It is the resources which act behind the scenes for DSC to achieve its “make it so” philosophy. DSC ships with a number of resources in-box and you can take a look here for the complete list. However, once you get started with DSC, you may want to write your own resources (which are PowerShell modules) for your particular use case. This post will walk you through creating a custom DSC resource. You can take a look here to get an understanding of the schema/packaging of DSC resources. Attached with this post is the Resource Designer Tool which makes writing resources a breeze.
There are certain rules which must be complied with when writing a DSC resource. I will state the rules towards the end of the post for reference and completeness. The Resource Designer Tool takes care of the nitty-gritty so that you can concentrate on implementing the features in your resource without having to get caught up in figuring out the correct MOF syntax or worrying about missing some important requirements in the resources (which is a PowerShell module) file.
Let us take an example. Say you want to create a resource for modelling an Active Directory User. (The User resource shipped in-box is for local users only.) In this example, we will have the following configuration properties in our schema MOF:
- UserName -> This will be the key property for our resource to uniquely identify a user. This is a required property
- Ensure -> This can only take two values: ‘Present’ and ‘Absent’ for stating whether we want the user account to be present or absent.
- DomainAdminCredential -> This will hold the domain account password for the user.
- Password -> This will hold the new password in case we wish to change an existing password.
Once we have the properties all set out, we can begin using the Resource Designer Tool. For this, create a folder named ‘DSCPack_ResourceDesigner’ anywhere inside your $env:PSModulePath, download the attachments and place them inside the folder you just created. After this, import the module (using ‘Import-Module DSCPack_ResourceDesigner’). We are all set. Let’s dive in now!
Let us see all the commands which we have as part of the designer tool:
PS C:\> (Get-Module DSCPack_ResourceDesigner).ExportedCommands
Key Value
--- -----
Import-DscSchema Import-DscSchema
New-DscResource New-DscResource
New-DscResourceProperty New-DscResourceProperty
Test-DscResource Test-DscResource
Test-DscSchema Test-DscSchema
Update-DscResource Update-DscResource
The first cmdlet we will be using is New-DscResourceProperty. Let us explore it.
PS C:\> help New-DscResourceProperty
NAME
New-DscResourceProperty
SYNOPSIS
Creates a DscResourceProperty to be used by New-DscResource.
SYNTAX
New-DscResourceProperty [-Name] <String> [-Type]< String> [-Attribute] {Key | Required | Read | Write}
[-ValidateSet<String[]>] [-Description <String>] [<CommonParameters>]
DESCRIPTION
Takes all of the given arguments and constructs a DscResourceProperty object to be used by New-DscResource.
RELATED LINKS
REMARKS
To see the examples, type: "get-help New-DscResourceProperty -examples".
For more information, type: "get-help New-DscResourceProperty -detailed".
For technical information, type: "get-help New-DscResourceProperty -full".
For every configuration property we want, we need to define it using this cmdlet. Let us see how we would define the various properties we stated earlier:
UserName:
PS C:\> $UserName = New-DscResourceProperty -Name UserName -Type String -Attribute Key
Ensure:
PS C:\> $Ensure = New-DscResourceProperty -Name Ensure -Type String -Attribute Write -ValidateSet"Present", "Absent"
DomainCredential:
PS C:\> $DomainCredential = New-DscResourceProperty -Name DomainAdminCredential -Type PSCredential -Attribute Write
Password:
PS C:\> $Password = New-DscResourceProperty -Name Password -Type PSCredential -Attribute Write
Now that the properties are defined, let us see how to generate the PowerShell module that will contain the implementation logic of the resource and the schema MOF file which will be consumed by DSC. The cmdlet we are looking for is New-DscResource.
PS C:\> help New-DscResource
NAME
New-DscResource
SYNOPSIS
Creates a DscResource based on the given arguments.
SYNTAX
New-DscResource [-Name] <String> [-Properties] <DscResourceProperty[]> [-Path] <String> [-ClassVersion< Version>] [-FriendlyName
<String>] [-Force] [-WhatIf] [-Confirm] [<CommonParameters>]
DESCRIPTION
Creates a .psm1 and .schema.mof file representing a Dsc Resource based on the properties and values passed in.
RELATED LINKS
REMARKS
To see the examples, type: "get-help New-DscResource -examples".
For more information, type: "get-help New-DscResource -detailed".
For technical information, type: "get-help New-DscResource -full".
Let us use the properties which we just defined to create the DSC resource.
PS C:\> New-DscResource -Name Demo_ADUser -Properties $UserName, $Ensure, $DomainCredential, $Password -Path 'C:\Program Files\WindowsPowerShell\Modules\DSCModules'
Here is the output we get:
Directory: C:\Program Files\WindowsPowerShell\Modules\DSCModules
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 11/13/2013 3:42 PM DSCResources
Directory: C:\Program Files\WindowsPowerShell\Modules\DSCModules\DSCResources
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 11/13/2013 3:42 PM Demo_ADUser
Looks like something exciting is going on here. A directory for our custom DSC resource has been created. Let us peek into what is in there.
PS C:\> cd 'C:\Program Files\WindowsPowerShell\Modules\DSCModules\DSCResources\Demo_ADUser'
PS C:\Program Files\WindowsPowerShell\Modules\DSCModules\DSCResources\Demo_ADUser> dir
Directory: C:\Program Files\WindowsPowerShell\Modules\DSCModules\DSCResources\Demo_ADUser
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 11/13/2013 3:42 PM 3770 Demo_ADUser.psm1
-a--- 11/13/2013 3:42 PM 700 Demo_ADUser.schema.mof
Let us take a look at the contents of the schema MOF:
[ClassVersion("1.0.0.0"), FriendlyName("Demo_ADUser")]
classDemo_ADUser : OMI_BaseResource
{
[Key] String UserName;
[Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
[Write, EmbeddedInstance("MSFT_Credential")] String DomainAdminCredential;
[Write, EmbeddedInstance("MSFT_Credential")] String Password;
};
The designer tool took care of the syntax of the MOF file for us. One of the nice things I liked about the tool when I first started using it was that it took care of deriving the class from ‘OMI_BaseResource’. I would always forget about this. Also take note of how it converted ‘ValidateSet’ – a term well known to PowerShell scripters into ‘ValueMap’ – a term specific to CIM schema language specification. Similarly it knows to convert ‘PSCredential’ into the type ‘MSFT_Credential’ which is understood by the DSC Engine.
Next, let us see what the psm1 file looks like (As explained here, this file contains the three ‘*-Target-Resource’ functions):
functionGet-TargetResource
{
[CmdletBinding()]
[OutputType([System.Collections.Hashtable])]
param
(
[parameter(Mandatory =$true)]
[System.String]
$UserName
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
<#
$returnValue = @{
UserName = [System.String]
Ensure = [System.String]
DomainAdminCredential = [System.Management.Automation.PSCredential]
Password = [System.Management.Automation.PSCredential]
}
$returnValue
#>
}
functionSet-TargetResource
{
[CmdletBinding()]
param
(
[parameter(Mandatory =$true)]
[System.String]
$UserName,
[ValidateSet("Present","Absent")]
[System.String]
$Ensure,
[System.Management.Automation.PSCredential]
$DomainAdminCredential,
[System.Management.Automation.PSCredential]
$Password
)
#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
}
functionTest-TargetResource
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory =$true)]
[System.String]
$UserName,
[ValidateSet("Present","Absent")]
[System.String]
$Ensure,
[System.Management.Automation.PSCredential]
$DomainAdminCredential,
[System.Management.Automation.PSCredential]
$Password
)
#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
This is the skeleton of the PowerShell Module generated by the tool. It contains the three functions that all DSC resources must define: Get-TargetResource, Test-TargetResource and Set-TargetResource. It also adds in some comments to help us with writing the resource. For example, take a look at the Test-TargetResource function. Since DSC is idempotent, we can apply the same configuration multiple times and if the current state is the same as the desired state, no action is taken. This is achieved by the DSC engine calling Set-TargetResourceonly if Test-TargetResource returns false. The tool hints at this by pointing out that Test-TargetResource should return a Boolean value. Also take note that the Get-TargetResource function contains the key property as its only parameter.
Given the skeleton of the generated module, we can proceed with adding our logic. After we are done writing our resource, we can see that it is one of the DSC resources added to the existing set:
PS C:\> Get-DscResource 'Demo_ADUser'
ImplementedAs Name Module Properties
------------- ---- ------ ----------
PowerShell Demo_ADUser DSCModules {UserName, …
At this point we can start writing our DSC configuration scripts using our new resource.
All this done, you realize you also need to add one more property which would be a hash table mapping a particular user to his last log in time. Oh, no do you need to re write the entire resource again? No! We got you covered:
S C:\> help Update-DscResource
NAME
Update-DscResource
SYNOPSIS
Update an existing DscResource based on the given arguments.
SYNTAX
Update-DscResource [-ModuleName]< String> [-Properties] <DscResourceProperty[]> [-ClassVersion
<Version>] [-FriendlyName<String>] [-Force] [<CommonParameters>]
DESCRIPTION
Update the .psm1 and .schema.mof file representing a Dsc Resource based on the properties and values
passed in.
RELATED LINKS
REMARKS
To see the examples, type: "get-help Update-DscResource -examples".
For more information, type: "get-help Update-DscResource -detailed".
For technical information, type: "get-help Update-DscResource -full".
So cool. Let us add the new property:
PS C:\> $lastLogOn = New-DscResourceProperty -Name LastLogOn -Type Hashtable -Attribute Write -Description "For mapping users to their last log on time"
Let us now update our resource:
PS C:\> Update-DscResource -ModuleName 'Demo_ADUser' -Properties $UserName, $Ensure, $DomainCredential, $Password, $lastLogOn -Force
The contents of ADUser.schema.mof have been updated:
[ClassVersion("1.0.0.0"), FriendlyName("")]
classADUser : OMI_BaseResource
{
[Key] String UserName;
[Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
[Write, EmbeddedInstance("MSFT_Credential")] String DomainAdminCredential;
[Write, EmbeddedInstance("MSFT_Credential")] String Password;
[Write, EmbeddedInstance("MSFT_KeyValuePair"), Description("For mapping users to their last log on time")] String LastLogOn;
};
If you take a look at the resource module, you will find that it has been updated too with the new parameter while all the logic you added are intact.
Let us cover one more cmdlet exposed by the designer tool:
PS C:\> help Test-DscSchema
NAME
Test-DscSchema
SYNTAX
Test-DscSchema [-Schema] <string> [[-SchemaCimClass]< ref>] [-errorIdsRef<ref>]
[<CommonParameters>]
ALIASES
None
REMARKS
None
You may have written a MOF schema by hand and want to test whether it will satisfy the conditions required by DSC. You could use the Test-Dscschema. Suppose we had the following schema:
[ClassVersion("1.0.0.0"), FriendlyName("")]
classDemo_ADUser : OMI_BaseResource
{
[Key] String UserName;
[Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
};
Say we save it as buggy.schema.mof.
PS C:\> Test-DscSchema -Schema .\buggy.schema.mof
Test-MockSchema : The Class name Demo_ADUser does not match the Schema name buggy.
At C:\Program
Files\WindowsPowerShell\Modules\DSCPack_ResourceDesigner\DSCPack_ResourceDesigner.psm1:2454 char:21
+ return (Test-MockSchema $Schema $SchemaCimClass)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId :ClassNameSchemaNameDifferentError,Test-MockSchema
False
There you go, it complains that the name of the class and the schema should be the same.
In this post we explored the Resource Designer Tool and saw how easy it is to create our own custom DSC resources. Hope you find it useful!
Given below are the rules which a DSC resource must conform to (this is just for reference and you do not need to worry about them if you use the designer tool):
1) At least one parameter in the schema is marked as [key]
2) All parameters MUST be marked with either key, required, write or read property. Additional rules as follows:
a. [Read] and ([key] or [required] or [write]) can’t coexist.
b. If multiple qualifiers are specified except [Read], then [key] takes precedence.
c. If [write] and [required] are specified, then [required] takes precedence.
3) Class from EmbeddedInstance MUST be either DSC system class or a class defined in the same scope. EmbeddedInstance class should not be DSC resource class i.e. derived from OMI_BaseResource class.
4) All classes MUST have [ClassVersion] qualifier.
5) Existence of Get-TargetResource, Set-TargetResource, and Test-TargetResource methods
6) Set/Test-TargetResource take all [key] and [write] parameters only.
7) Get-TargetResource takes all [key] parameters and optionally can have [required] as well as [write] parameters
8) Get/Set/Test-TargetResource do not have any parameters not defined in the schema
9) Get/Set/Test-TargetResource should have key/required parameter marked as mandatory
Abhik Chatterjee
Windows PowerShell Developer
Microsoft
For more details refer to DSCPack_ResourceDesigner