Creating a custom PowerShell v4 DSC Resource

We have touched on how to create PowerShell c4 DSC Resources briefly as we have worked our way through this book, and here, we will dive into the details of doing so in depth. We will cover the folder structures and files needed as well as the mindset and best practices necessary in order to make custom DSC Resources that are effective and useful to you.

Note

Before continuing, ensure that you are familiar with creating PowerShell v2 modules. Knowing how to create and use PowerShell modules is the key in understanding how DSC Resources are made.

The folder structure of a v4 DSC Resource

A v4 DSC Resource has a strict folder structure. By convention, each PowerShell module that contains one or more DSC Resources is stored in the $env:ProgramFilesWindowsPowerShellModules folder. You can store DSC Resources in any of the standard $env:PSModulePath paths, or you can store them in a location of your choosing, so long as you append the location to the $env:PSModulePath variable before compiling the Resource.

The strict folder structure for v4 DSC Resources is as shown:

$env:ProgramFilesWindowsPowerShellModules
  |- ExampleModule
    |- ExampleModule.psd1
    |- DSCResources
    |- Example_InstallFoo
      |- Example_InstallFoo.psd1 (optional)
      |- Example_InstallFoo.psm1 (required)
      |- Example_InstallFoo.schema.mof (required)
    |- Example_ConfigureFoo
      |- Example_ConfigureFoo.psd1 (optional)
      |- Example_ConfigureFoo.psm1 (required)
      |- Example_ConfigureFoo.schema.mof (required) 

A folder called DSCResources inside the root folder, with other folders that are also DSC Resources themselves, and files with schemas separate from the code module files? Like with the DSC composite resource, you must be wondering why they make this so hard! It is difficult to keep straight at first, but once you have done one or two DSC Resources, it's not really that hard to keep straight. It is easier to understand once you see that each DSC Resource is made up of one or more PowerShell modules.

A DSC Resource contains one or more PowerShell modules inside a "root" PowerShell module. This root PowerShell module declares the DSC Resource and has the information necessary for the DSC engine to load the modules into memory. The root module typically does not contain any code itself, as it won't be available without significant effort to the submodules. Each submodule in the DSCResources folder represents an actual DSC Resource. Inside each module folder is a .psm1 file and a .mof file. The .psm1 file is the file that contains all of the executable code that DSC uses to determine and set the state on the target node. The .mof file contains schema data that describes the parameters in use for a particular DSC Resource.

Let's look at an example DSC Resource provided by Microsoft instead of a contrived example like the preceding folder structure. We will look at xTimeZone as it is a relatively simple resource that has one submodule and few files. Inside the root module folder, we can see the DSCResources folder that holds all the submodules, as shown:

[PS]> ls $env:ProgramFilesWindowsPowerShellModulesxTimeZone

    Directory: C:Program FilesWindowsPowerShellModulesxTimeZone

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----          7/9/2015  10:11 PM            DSCResources
-a---          7/9/2015  10:11 PM        969 xTimeZone.psd1
-a---          7/9/2015  10:11 PM       1988 xTimeZone.Tests.ps1

As you can see listed in the preceding structure, the main DSC Resource folder also contains a folder called DSCResources. The DSCResources folder contains all the actual DSC Resource modules. The "root" folder, in this case xTimezone, is our container module. It holds all the information needed to discover and parse DSC Resources, namely the xTimeZone.psd1 file. This is the basic PowerShell module format and function here, nothing new. The DSCResources subfolder is a special folder; the DSC engine looks for that folder, and if present, will know to enumerate that directory for DSC Resources to load.

Tip

If you see more files on your copy of xTimeZone than we list here, the reason for the difference is that we removed extraneous files to simplify our example.

Looking at the DSCResources folder, we see the following structure:

 [PS]> ls $env:ProgramFilesWindowsPowerShellModulesxTimeZoneDSCResources

    Directory: C:Program FilesWindowsPowerShellModulesxTimeZoneDSCResources

Mode           LastWriteTime     Length Name
----           -------------     ------ ----
d----       7/9/2015  10:11 PM     xTimeZone

As we can see in the preceding listing, the DSCResources folder contains all the actual DSC Resource module folders. In this case, we see a folder called xTimeZone, which is the same name of the root module. In our next example, we'll see one that diverges from this and find out why it does, but for now, let's continue down the path we're on.

Looking at the xTimeZone folder, we see two files, at which point we have come to the last stop in our tour of a DSC Resource:

[PS]> ls $env:ProgramFilesWindowsPowerShellModulesxTimeZoneDSCResourcesxTimeZone

    Directory: C:Program FilesWindowsPowerShellModulesxTimeZoneDSCResourcesxTimeZone

Mode             LastWriteTime           Length Name
----            -------------            ------ ----
-a---          7/9/2015  10:11 PM      3194 xTimeZone.psm1
-a---          7/9/2015  10:11 PM      173 xTimeZone.schema.mof

These two folders contain the actual meat of a DSC Resource, and hold all information needed for the DSC engine to parse and use a DSC Resource on a target node. The xTimeZone.psm1 file holds the PowerShell code that operates on the target node. The xTimeZone.schema.mof file holds the MOF schema that defines the parameter statement for the DSC Resource. We will cover these two files in detail in the following section.

The syntax of a v4 DSC Resource

The PowerShell v4 DSC Resource syntax can be described by dividing it up into the DSC Resource definition file and the DSC Resource PowerShell module file.

The DSC Resource definition file

A DSC Resource definition file is an MOF schema file that has a specific CIM syntax. It is at first very obtuse and unfriendly to look at, but becomes easier to read the more you work with it. The MOF files are supposed to be easy for the machine to read and not you, so there is some initial effort in understanding the syntax and format for them. Don't worry too much about this as you aren't meant to look at it yourself much. MOF schema files are expected to be automatically created and updated using the DSC tooling Microsoft has released.

The main purpose of a DSC Resource MOF schema file is to provide the name and version of the DSC Resource and to declare the parameters the DSC Resource will accept. An example of a DSC Resource MOF schema file is as shown:

[ClassVersion("1.0.0"), FriendlyName("InstallFoo")]
class Example_InstallFoo: OMI_BaseResource
{
  [Key] string Name;
  [Required] string PhysicalPath;
  [write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure;
};

Note

If you are reading CIM syntax, class names, and writeable values and starting to sweat, don't worry. Your fears will be allayed in the Authoring custom DSC Resources section.

There is still benefit in going over the syntax of the MOF schema file, as you might have to troubleshoot issues in parsing when creating your own DSC Resources or troubleshoot DSC Resources created by others. If this section is getting through to you, you can reference it later when the time comes.

Naming

There are two DSC Resource names to keep track of, the CIM class name (sometimes referred to as the fully qualified name) and the friendly name. The CIM class name is the name that DSC uses internally to refer to a DSC Resource, and the friendly name is typically a shorter and easier to read version for a human.

You have to be careful in specifying these names, as your choice in which one to include determines how your users ask for your DSC Resource. If you specify the same name for both, then a user uses that name when they ask for the DSC Resource to be imported. If you specify the CIM class name but not the friendly name, then the user has to specify the CIM class name. If you specify the friendly name, then the user must use that to import your DSC Resource and cannot use the CIM class name. If all this sounds confusing, do like the author does and keep it simple by setting both names the same when authoring DSC Resources.

We can simplify naming the DSC Resource in the MOF example we used earlier, as shown:

[ClassVersion("1.0.0"), FriendlyName("InstallFoo")]
class InstallFoo: OMI_BaseResource
{
  [Key] string Name;
  [Required] string PhysicalPath;
  [write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure;
};

Note

Why the complicated naming? It was explained that these different names account for situations where DSC Resources are named the same and allow the user to specify which ones to load and use. As we will see in the Creating a custom PowerShell v5 DSC Resource section, this concept has been simplified greatly and is easier to use.

Versioning

The second piece of information to include is the DSC Resource version. Versioning your DSC Resources is very important during the lifetime of use of your DSC Resource. Along with the DSC Resource name, users can specify an exact version to load. This ensures they will get the exact DSC Resource they are expecting.

Parameters

The most important piece of information in the DSC MOF schema file is the property declarations.

Tip

CIM uses the term "property," and PowerShell uses "parameter" to refer to the same thing. It's easy to get confused by this, and some people use the terms interchangeably.

As we will cover in The DSC PowerShell module file coming up, the parameters or properties the DSC Resource accepts have to match each of the TargetResource functions. This rule also applies to the MOF schema file.

The parameter statement has the same information as the parameter statement in the TargetResource functions, but it looks drastically different. This is because it is using the more verbose CIM syntax, which is less pleasant on the eyes than the PowerShell syntax. There are a few rules to keep in mind when specifying the parameter set here, and we will cover those in the next section when we author our own DSC Resources.

Qualifiers

Each parameter can have one or more qualifiers: Key, Required, Read, and Write. These qualifiers describe what the parameter is and what it is allowed to do, listed as follows:

  • The Key qualifier indicates that the parameter is mandatory and is used to uniquely identify the DSC Resource in the DSC configuration script file. This means that the value for this parameter has to be unique in the script file and has to be singular, as well; it cannot be an array or a collection. It must also be a string or numeral value, it cannot be any other type. All DSC Resources are required to have at least one Key property. For DSC Resources with more than one Key property defined, DSC uses a combination of all Key properties put together as the Key qualifier used.
  • The Required qualifier indicates that the parameter is mandatory but is not required to be unique for the DSC Resource.
  • The Write qualifier is used to pass information and values to the DSC Resource, but is not required for successful execution of the DSC Resource. If used, this parameter is not mandatory.
  • The Read qualifier is only used in the Get-TargetResource function, as it returns information only.

The DSC PowerShell module file

The DSC PowerShell module file (or implementation file) is the file where the actual code that determines a target node's state resides. The DSC PowerShell module file has the same name as what was declared as the CIM class name in the DSC Resource definition file.

Each DSC Resource module file (the PowerShell .psm1 file) is required to contain three functions with specific names in order to work. Each function is required to have the same parameters that were defined in the DSC Resource definition file. Any deviation in parameter set declarations (typos, missing or added parameters, conflicting types, and so on) between functions is not allowed.

Note

Adding up all the places in which you must keep track of parameter statements ultimately makes four places you need to keep track of. If this sounds to you like a recipe for syntax errors and forgotten parameters, then you aren't alone. Keep this in mind when we get to the next section on v5 DSC Resources.

Get-TargetResource

The Get-TargetResource function is responsible for reporting on the current state of the system. This function will never change the system; it is read-only. Instead, it inspects the system state and returns a PowerShell hash containing the information obtained.

This function is required to implement all Key and Required properties defined in the DSC Resource definition file. The Write (non-mandatory) parameters do not have to be included. In practice, many people include all properties in an effort to not miss out on including important ones.

A Get-TargetResource function is required to return a single PowerShell hashtable that contains the information collected from the target system. This hashtable must contain all parameters that were defined in the DSC Resource definition file, as well as their corresponding values.

Test-TargetResource

The Test-TargetResource function is similar to the Get-TargetResource function in that it inspects the current state of the system, but it takes things one step further. After inspecting the current state of the system, it compares it to the expected values provided by the user. Depending on the way the values are specified, it tests to see if they match.

This function is required to implement all Key, Required, and Write parameters defined in the DSC Resource definition file. Key and Required properties are required to be defined as mandatory parameters.

The Test-TargetResource function always returns a Boolean value of either $true or $false. If the target node is in the desired state, the function should return $true. If the target node is not in the desired state, it should return $false.

Set-TargetResource

The Set-TargetResource function actually brings the state of the system to the desired state. This is the place where idempotency comes into play; the PowerShell code here is responsible for detecting the current state and only changing it if it does not match what it is expected.

This function is required to implement all Key, Required, and Write parameters defined in the DSC Resource definition file. Key and Required properties are required to be defined as mandatory parameters.

A Set-TargetResource function does not return any value to the calling code. This function either succeeds or fails; it does not indicate any state in between. Failure is indicated by throwing an exception with message text indicating the failure. Success is indicated by the function completing without error.

Optionally, a Set-TargetResource function can support the WhatIf PowerShell behavior and provide a summary of what would be changed on a target node without actually changing the target node state. The function can support this by implementing the PowerShell's SupportsShouldProcess feature.

If the target node must be restarted after Set-TargetResource has successfully implemented the changes it was instructed to do, it can indicate this to the DSC engine by setting the global variable $global:DSCMachineStatus to 1 before exiting.

Authoring custom DSC Resources

Now that we have covered the syntax and structure of DSC Resources, we can finally cover how to make your own DSC Resources. The fun begins!

Creating DSC Resources manually

The secret to creating DSC Resources manually is: to not create DSC Resources manually. Seriously; there are easier ways and you needn't put yourself through the pain. As with most things that have a lot of easy-to-reproduce structure, we can use tools to perform most of the grunt work for us. This is not just to avoid doing extra work; this removes the possibility of getting something wrong due to human error.

Creating DSC Resources automatically

While you can create DSC Resources manually by creating the files and folders required by hand, you do not have to. Microsoft released a PowerShell module as part of their DSC Resource Kit that automates creating DSC Resources called xDscResourceDesigner.

Using the xDscResourceDesigner module requires a little upfront thought and planning, but returns that investment in making DSC Resources that are syntactically correct and properly arranged easily and without much fuss.

Before you start to create your DSC Resource, you must make a decision on the following points:

  • Decide the name, friendly name, and version of the DSC Resource you are creating
  • Decide the location you are going to create the files in
  • Decide the parameters you are going to use in your DSC Resource

Once you have this information decided in creating the DSC Resource, using the xDscResourceDesigner module is as easy as these two steps:

  1. Create a variable for each DscResourceProperty (or, as we have been calling them, parameters) using the New-xDscResourceProperty Cmdlet.
  2. Pass the array of variables created to the New-DscResource Cmdlet.

Putting them all together, here is an interactive session that creates the xExampleResource with two parameters:

 [PS]> $Ensure = New-xDscResourceProperty -Name "Ensure" -Type "String" -Attribute Write -ValidateSet "Present","Absent" -Description "Ensure Present or Absent"
[PS]> $ExampleSetting = New-xDscResourceProperty -Name "ExampleSetting" -Type "String" -Attribute Key -Description "Set an Example Setting"
[PS]> New-xDscResource -Name "xExampleResource" -Property $Ensure,$ExampleSetting -Path "$env:ProgramFilesWindowsPowerShellModulesxExampleResource" -ClassVersion 1.0 -FriendlyName "Example" -Force

    Directory: C:Program FilesWindowsPowerShellModules

Mode                LastWriteTime          Length Name
----                -------------          ------ ----
d----              7/9/2015   9:57 PM    xExampleResource

    Directory: C:Program FilesWindowsPowerShellModulesxExampleResource

Mode                LastWriteTime          Length Name
----                -------------          ------ ----
d----              7/9/2015   9:57 PM      DSCResources

    Directory: C:Program FilesWindowsPowerShellModulesxExampleResourceDSCResources

Mode                LastWriteTime     Length Name
----               -------------     ------ ----
d----            7/9/2015   9:57 PM  xExampleResource

The preceding code creates the following resource:

[PS]> ls $env:ProgramFilesWindowsPowerShellModulesxExampleResourceDSCResourcesxExampleResource

    Directory: C:Program FilesWindowsPowerShellModulesxExampleResourceDSCResourcesxExampleResource

Mode      LastWriteTime                  Length Name
----      -------------                   ------ ----
-a---   7/16/2015   9:57 PM       3004 xExampleResource.psm1
-a---   7/16/2015   9:57 PM        594 xExampleResource.schema.mof

Looking at xExampleResource.schema.mof, we see New-xDscResource did all the heavy lifting for us and not only set up the CIM class for us but also correctly set all the parameter statement information we needed:

[ClassVersion("1.0"), FriendlyName("Example")]
class xExampleResource : OMI_BaseResource
{
    [Write, Description("Ensure Present or Absent"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
    [Key, Description("Set an Example Setting")] String ExampleSetting;
};

Inspecting xExampleResource.psm1, we see that New-xDscResource created stub TargetResource functions for us with example code. More importantly, it added the parameters in the correct formats to each function:

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $ExampleSetting
    )

    #Write-Verbose "Use this cmdlet to deliver information about command processing."
    #Write-Debug "Use this cmdlet to write debug information while troubleshooting."

    <#
    $returnValue = @{
        Ensure = [System.String]
        ExampleSetting = [System.String]
    }

    $returnValue
    #>
}


function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure,
        [parameter(Mandatory = $true)]
        [System.String]
        $ExampleSetting
    )

    #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
}


function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure,
        [parameter(Mandatory = $true)]
        [System.String]
        $ExampleSetting
    )

    #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 

So, we have seen that the Cmdlets from the xDscResourceDesigner module are really useful in making the authoring of custom DSC Resources easier and quicker, while at the same time maintaining correct syntax and structure. This is all well and fine if we know ahead of time all the parameters and properties we want to use in our DSC Resource. What happens if we use these cmdlets, write a bunch of code, and then find out we need another parameter? Do we have to manually add it ourselves and risk getting the syntax wrong? Never fear; Update-xDscResource is here!

To use Update-xDscResource, all we have to do is create a new variable for our new parameter and point Update-xDscResource at it, and it will update the existing DSC Resource with our new parameter, as shown:

[PS]> $ExampleFooBar = New-xDscResourceProperty -Name "FooBar" -Type "String" -Attribute Write -Description "Set an Example FooBar"
[PS}> Update-xDscResource -Name "xExampleResource" -Property $ExampleFooBar,$Ensure,$ExampleSetting -ClassVersion 1.0 -Force

Looking at the MOF schema file, we see our new parameter added:

[ClassVersion("1.0"), FriendlyName("Example")]
class xExampleResource : OMI_BaseResource
{
    [Write, Description("Set an Example FooBar")] String FooBar;
    [Write, Description("Ensure Present or Absent"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
    [Key, Description("Set an Example Setting")] String ExampleSetting;
};

If you look back at our Update-xDscResource statement, you'll notice we specified our previous variables as well as our new one. This is the one inconvenience with using Update-xDscResource; you have to provide the same set of parameters as you did when you first created the DSC Resource when you want to update it with new ones. This is not too much of an imposition when you are actively making a DSC Resource, but becomes more of a problem when you come back to a DSC Resource after months elapse. You most likely won't have saved the text you used to create the DSC Resource and will have to create it all over again.

While you are in active development of a DSC Resource, you could keep around in a text file the commands you used to create it for easier updating later. You could also serialize your parameter statement to a file using the Export-Clixml Cmdlet and then reimport it at a later time using Import-Clixml:

$params = $Ensure,$ExampleSetting,$FooBar
$params | Export-Clixml –Path .myDscParams.xml

# time passes

$params = Import-Clixml –Path .myDscParams.xml

Update-xDscResource -Name "xExampleResource" -Property $params -ClassVersion 1.0 –Force

How you choose to handle this depends largely on how much you expect your DSC Resource properties to change in the future.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset