Desired State Configuration (DSC) 201 - Custom Actions

Posted by Daniel Mann - May 08, 2014

header-picture

In my last blog post, Desired State Configuration 101 – An intro to DSC, I talked a little bit about what Desired State Configuration is, what you can do with it, and showed a sample DSC script that installs the MSMQ Windows feature on a server.

Today, I'm going to expand our script to do a little bit more... We're going to use it to manage our MSMQ queues!

In the last post, we ended up with this script to install the MSMQ feature:

[powershell]
Configuration InstallMSMQFeature
{
param($Servers="localhost")
Node $Servers
{
WindowsFeature MessageQueueFeature
{
Ensure = "Present"
Name = "MSMQ"
}
}
}

InstallMSMQFeature -Servers "dsclab", "foo"
Start-DscConfiguration -Wait -Verbose -Path .\InstallMSMQFeature

[/powershell]

How do we do this? Easy! We can build a custom resource! A custom resource is just a bunch of PowerShell code we can invoke in order to set up our resource, just like the WindowsFeature resource we looked at in the last blog post.

Folder structure for a custom resource

The first step is putting our scripts in the right folder structure so PowerShell can discover them. To do that, you can look at the $env:PsModulePath variable.

On my machine, it looks like this:


PS C:\Users\daniel.mann\Desktop> $env:PSModulePath
C:\Users\daniel.mann\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules\

So, I'm going to put my scripts in C:\Program Files\WindowsPowerShell\Modules\.

Under this folder, I need to create a folder structure like this:

[powershell]
My Resource Name (folder)
DSCResources (folder)
My Resource Name.psd1 (file)
Individual Action Name (folder)
Individual Action Name.psm1 (file)
Individual Action Name.schema.mof (file)
[/powershell]

Now, we're seeing three file types we might not be familiar with here. They are:

PSD1 - This is a PowerShell module manifest -- this defines some info about our module
PSM1 - This is a PowerShell module script file, which contains our script code
schema.mof - This is a Managed Object Format definition. This is where we define what properties our script will take.

I'm going to name my module "MSMQResources" and my custom resource "MSMQManager".

First off, make sure the folder structure outlined above exists. Then we can get started by making our module manifest. This is really easy using the New-ModuleManifest PowerShell cmdlet!

Making our module manifest

Just run:


New-ModuleManifest -Path "C:\Program Files\WindowsPowerShell\Modules\MSMQResources\MSMQResources.psd1"

And now the manifest exists! You can edit it if you want, although you don't actually need to in this case.

Creating a MOF schema

Next up, we can make our MOF schema. I'm not going to dig too deep into all of the pieces of a MOF schema in this blog post... you can copy and paste my schema and modify it as you see fit. A really useful article when modifying this thing is MOF Data Types, from MSDN.

Here's what my MSMQManager.schema.mof file looks like:
[powershell]
[ClassVersion("1.0.0"), FriendlyName("Queue")]
class MSMQManager : OMI_BaseResource
{
[Key]string Name;
[write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure;
};
[/powershell]

The important pieces here are the "FriendlyName" attribute and the parameters, "Name" and "Ensure".

"FriendlyName" just exposes a name that we can reference this action by in our DSC script.
Then we have two parameters:
"Name", which is what we want to call our queue
"Ensure", which is what we can use to specify whether the queue should exist on our server or not. I'm using a "ValueMap" here, so the only allowable values are "Present" and "Absent". You can make whatever values you want be acceptable by changing the ValueMap.

I saved this file to C:\Program Files\WindowsPowerShell\Modules\MSMQResources\DscResources\MSMQManager.schema.mof

Creating our custom resource

Now for the fun part: Actually writing some code!

We need to implement three functions in our custom resource, and each of these functions has to take the parameters we defined in our MOF schema.

The functions are:

Get-TargetResource
Set-TargetResource
Test-TargetResource

Get-TargetResource queries information about the thing that we're interested in configuring. In this case, a single message queue. It should return a hash table containing all of the properties that someone would be interested in knowing about a message queue -- its name, whether it exists, and so forth.

Set-TargetResource is the function that's responsible for changing the message queue. This is where you create a queue if it should exist but doesn't, delete it if it shouldn't exist, or reconfigure it if it's not set up correctly.

Test-TargetResource also queries information about the thing that we're interested in configuring, but in this case, should return a Boolean value depending on whether the item exists or not. If it returns True, that means no actions have to be taken -- everything is okay. If it returns False, that means that the queue isn't in the correct state and needs corrective action taken.

With that in mind, here's my script. I saved it as C:\Program Files\WindowsPowerShell\Modules\MSMQResources\DscResources\MSMQManager.psm1

[powershell]
function Get-TargetResource
{
[OutputType([Hashtable])]
param(
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Name,
[ValidateSet('Present','Absent')][string]$Ensure = 'Present'
)

[System.Reflection.Assembly]::LoadWithPartialName("System.Messaging") | out-null
$queueExists = [System.Messaging.MessageQueue]::Exists(".\private$\$Name")

return @{Name="$queue.Name"; Ensure=$queueExists}

}

function Set-TargetResource
{
param(
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Name,
[ValidateSet('Present','Absent')][string]$Ensure = 'Present'
)

[System.Reflection.Assembly]::LoadWithPartialName("System.Messaging") | out-null

$queueExists = [System.Messaging.MessageQueue]::Exists(".\private$\$Name")
if ($Ensure -like 'Present') {
if (!$queueExists) {
[System.Messaging.MessageQueue]::Create(".\private$\$Name")
}
}
else {
if ($queueExists) {
[System.Messaging.MessageQueue]::Delete(".\private$\$Name")
}
}
}

function Test-TargetResource
{
[OutputType([boolean])]
param(
[Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Name,
[ValidateSet('Present','Absent')][string]$Ensure = 'Present'
)

[System.Reflection.Assembly]::LoadWithPartialName("System.Messaging") | out-null
$queueExists = [System.Messaging.MessageQueue]::Exists(".\private$\$Name")

if ($Ensure -like 'Present') {
if ($queueExists) {
return $true
}
else {
return $false
}
}
else {
if (!$queueExists) {
return $true
}
else {
return $false
}
}
return $false
}
[/powershell]

Validating the custom resource

Now, with the folder structure created and the three pieces in place, we can run a quick PowerShell command to check if the resource is available. Get-DscResource returns all of the DSC resources that are installed.


PS C:\Users\daniel.mann\Desktop> get-dscresource -Name Queue
ImplementedAs Name Module Properties
------------- ---- ------ ----------
PowerShell Queue MSMQResources {Name, DependsOn, Ensure}

 

If you see that, you're all set!

Extending our DSC script

Our custom resource is all set up, so we can modify our existing script to take advantage of it.

First, within our Configuration block, we need to tell DSC to import our custom resource:

[powershell]
Configuration CreateMessageQueues
{
...
Import-DscResource -Name MSMQManager
...
}
[/powershell]

Then, within our Node, we need to add a Queue resource to tell DSC that we want our queue to exist, and to specify the name.

We're going to be using a feature of DSC that I haven't talked about yet, too: DependsOn. With this, we can tell DSC that it depends on another resource executing. This allows DSC to only execute the things it needs to, and to execute them in the correct order, automatically!

[powershell]
...
Node $Servers
{
...
WindowsFeature MessageQueueFeature { }
...
Queue MyWonderfulQueue
{
Ensure = "Present"
Name = "FooQueue"
DependsOn = "[WindowsFeature]MessageQueueFeature"
}
}
[/powershell]

Now, our completed script:
[powershell]
Configuration CreateMessageQueues
{
param($Servers="localhost")
Import-DscResource -Name MSMQManager

Node $Servers
{
WindowsFeature MessageQueueFeature
{
Ensure = "Present"
Name = "MSMQ"

}

Queue FooQueue
{
Ensure="Present"
Name="Foo"
DependsOn="[WindowsFeature]MessageQueueFeature"
}
}
}

CreateMessageQueues
Start-DscConfiguration -Wait -Verbose -Path .\CreateMessageQueues
[/powershell]

And that's all there is to it! When I run this script, it will validate that the Message Queue feature is installed, and that my Foo queue exists.

Topics: Blog


Recent Posts

InCycle Named Azure Data Explorer (ADX) Partner

read more

OpsHub & InCycle Help Top Medical Device Company Accelerate Innovation

read more

InCycle Continues to Lead, Recognized by Microsoft for Industry Innovation. Earns Impact Award.

read more