Creating a custom PowerShell v5 DSC Resource

As we have progressed through this chapter, we have made some observations about PowerShell v4 DSC Resources. We have seen that DSC Resource MOF schema files are verbose and obtuse. CIM schemas requires many lines to express a simple parameter statement, and the wording used is confusingly different from the wording used inside the PowerShell scripts. Managing both MOF schema files and PowerShell scripts files with essentially the same information is ripe for human error and mistakes.

Microsoft listened to this feedback and came back with PowerShell v5 DSC class-based DSC Resources.

PowerShell classes

PowerShell v5 introduced a new feature called a PowerShell class. If you are familiar with programming in any language, you will recognize this new feature. If not, don't worry about it too much as you can get pretty far with some basic programming knowledge that you already learned using PowerShell. That is one of the original design goals in Jeffery Snover's Monad Manifesto—to create PowerShell as a gateway step to C# for those so inclined.

Since you're already adept at using PowerShell, you know anything and everything PowerShell operates on and produces is a class, specifically a class in the .NET Framework. Run Get-Process, and you get a collection of System.Diagnostics.Process objects.

We won't get too far into discussing classes here, as that is a subject many books have already been written about. For our purposes, think of a class as a grouping of properties and functions inside one place that can be created to perform specific functions. PowerShell classes are designed to provide similar functionality.

PowerShell classes are very similar to PowerShell functions (by design), so they are easy to understand once you grasp some simple concepts. Let's use a common example—a class that defines a point, something that represents a location on the computer screen. We would need to describe this point's location using x and y coordinates and also have some way to adjust those coordinates to move the point to a new location. This is accomplished in a PowerShell class like so:

class Point
{
    $X = 0
    $Y = 0

    [void] Move($xOffset, $yOffset)
    {
        $this.X += $xOffset
        $this.Y += $yOffset
    }
}

Not too scary, right? Looks like a PowerShell function for the most part. We declare properties with normal PowerShell variable syntax and declare the class method using PowerShell function syntax. We can create a point object and move it like so:

$point = [Point]::new()
$point.Move(10, 20)
$point

Again, this book can't begin to cover the programming concepts you would need to get started. It is enough to describe the concepts we are going to use with DSC Resources.

Class-based DSC Resources

We have stated that class-based DSC Resources provide several benefits over PowerShell v4 DSC Resources. The largest reasons for these benefits are the folder structure and file improvements that class-based DSC Resources use.

The folder structure of v5 DSC Resources

A class-based DSC Resource has a much simpler folder structure than a module-based DSC Resource. The submodule folder structure underneath a root DSCResources folder with extra .psd1 files and multiple schema and .psm1 files that need to be matched are gone. In its place are a simple one folder structure and two PowerShell files, a .psd1 and .psm1 file, as shown:

[PS]> ls $env:ProgramFilesWindowsPowerShellModulesxExampleClassBasedDSCResource

    Directory: C:Program FilesWindowsPowerShellModulesxExampleClassBasedDSCResource

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---          7/9/2015  10:11 PM       3194 xExampleClassBasedDSCResource.psm1
-a---          7/9/2015  10:11 PM        173 xExampleClassBasedDSCResource.psd1

What about grouping similar DSC Resources into one distributable package like we discussed earlier? Does moving to class-based Resources do away with that? Not at all. Instead of multiple folders and files, all your DSC Resources go into one file. Since each DSC Resource is a self-contained class, it's not as messy as you think and actually organizes things better than the previous approach.

The syntax of v5 DSC Resources

While the class-based DSC Resource folder structure is much more relaxed, the syntax for a class-based DSC Resource is rather strict, or slightly more so. This makes sense, as we are collapsing a lot of functionality into one file and so need a reliable way of parsing the files to get the same information we got previously in several separate files.

Declaring the class

Instead of a PowerShell function declaration, we use a PowerShell class declaration to define our DSC Resource:

[DscResource()]
class FooBar
{
    <# Class code here #>
}

Instead of function, we use class, so not too much difference. We added a new attribute called DscResource, but if you are familiar with PowerShell v2 parameter attributes, using attributes to denote supported functionality should be easy to transition to. We use the DSCResource attribute to indicate that this class is a DSC Resource. This is done so that the DSC engine can locate and parse classes as DSC Resources.

Schema

A class-based DSC Resource schema defines the properties of the class that can be used by the user of the DSC Resource. Instead of a separate MOF file, the properties are defined inside the class using PowerShell attribute syntax on top of PowerShell variable declarations.

Declaring DSC properties is a simple as this:


[DscResource()]
class FooBar
{
    [DscProperty(Key)]
    [string]$Wakka

    [DscProperty(Mandatory)]
    [string]$Path


    <# Class code here #>
}

There is one attribute that can be used here, DscProperty, but several values that can be passed to it to indicate what type of DSC property is being used here.

Property

MOF equivalent

Description

DscProperty(Key)

Key

The property is a key for the DSC Resource and required

DscProperty(Mandatory)

Required

The property is required

DscProperty(NotConfigurable)

Read

The property is read-only and is populated by the Get() function

DscProperty()

Write

This is a configurable property that is not required

Methods

Declaring a class and a few properties is not all that is required for a class-based DSC Resource. Remember Get-TargetResource, Test-TargetResource, and Set-TargetResource in the module-based DSC Resource in the previous section? In the class-based DSC Resource, we have simplified this into three methods with the same prefix, all in the same class. Let's build on the previous example and add the required methods:

[DscResource()]
class FooBar
{
    [DscProperty(Key)]
    [string]$Wakka

    <# Class code here #>

    [FooBar] Get() {}
    [bool] Test() {}
    [void] Set() {}
}

Notice that as before with the module-based DSC Resources, each method returns a specific value or none at all.

Get

The Get method returns an instance of the class instead of a hashtable, with the properties populated with the discovered values. In our preceding example, the Get method returns an instance of the FooBar class. If we were to implement the Get method, it would look like this:

[FooBar] Get()
{
    $present = $this.TestForWakka($this.Path)
    if ($present)
    {
        $this.Wakka = $this.Path
    }
    else
    {
        $this.Wakka = $null
    }

    return $this
}

The internal code is contrived, so forgive the silliness, but it proves the point. This tests a few things and sets the properties with values based on those tests. Notice the use of the special variable $this; it's an automatic class variable that refers to the instance of the class itself. You can use it to access methods and properties defined in the class.

Test

The Test method is the equivalent of the Test-TargetResource function and returns a Boolean indicating the state of the target node. If the node is in the expected state, it returns $true. If it is not in the expected state, it returns $false. Consider the following example:

[bool] Test()
{
    $present = $this.TestForWakka($this.Path)
    if ($present)
    {
        return $true
    }
    else
    {
        return $false
    }
}

Set

The Set method is the equivalent of the Set-TargetResource function. The Set method executes the code necessary to bring the target node to compliance. Like module-based DSC Resources, it does not return a value and instead indicates success by not throwing an exception.

Implementing the Set method will vary greatly by your problem domain, but here is an example of a Set method that operates on information it finds on the target node:

[void] Set()
{
  $fileExists = $this.TestFilePath($this.Path)
  if($this.ensure -eq [Ensure]::Present)
  {
    if(-not $fileExists)
    {
      $this.CopyFile()
    }
  }
  else
  {
    if($fileExists)
    {
      Remove-Item -LiteralPath $this.Path -Force
    }
  }
}

The advantages of a class-based DSC Resource

So, how does having PowerShell classes benefit us with DSC Resources? A PowerShell class defines both the schema and implementation of the DSC Resource in the same file, greatly simplifying the authoring and use of DSC Resources. Instead of the verbose folder structure we covered in the Creating a custom PowerShell v4 DSC Resource section, we have a single class that handles everything for us.

PowerShell v4 DSC Resources require a specific folder structure and many files in order to work correctly. There is a lot of boilerplate code and repetition in declaring the properties and functions in order to make things work. The separate MOF schema file has to be kept in sync with the PowerShell module file implementing the Get, Set, and Test –TargetResource functions, which means manual human intervention. This is an error-prone process that is easily forgotten about when trying to make a DSC Resource work.

Perhaps the easiest to overlook but most important part of a class-based DSC Resource is the type safety provided. Type safety is a programming term that means a certain object, method, or property can only ever be the thing we assigned it. The methods and properties of a DSC class only return the information we expect, which returns runtime errors due to programming mistakes.

In PowerShell v4, there was no way to directly invoke the Get-TargetResource functions using DSC against a target node yourself, so the function had little utility in interactive use. In v4, it wasn't used internally by the DSC engine as much as it is in v5, so there were not very many people who bothered to implement the function. Instead, they declared the function and had it return an empty hash. In PowerShell v5, you can directly invoke the Get-TargetResource function using Invoke-DscResource, which brings a lot of utility and purpose to this function. It is assumed that this function will really shine when there is a "reporting" feature to DSC. Currently, there isn't a way to report on the current state of target nodes using DSC. This information is stored after a DSC run is performed on a target node.

The disadvantages of a class-based DSC Resource

The main disadvantage of class-based DSC Resources is that you cannot use them in an environment that has both PowerShell v4 and PowerShell v5 installed, while module-based DSC Resources work fine in either version. This makes transitioning hard as it is unlikely you will be able to upgrade an entire environment at once to PowerShell v5. This will influence your decisions on making custom DSC Resources now, as you will have to anticipate and plan for the version of PowerShell that is on the target host in the future.

This is especially important if you are making a DSC Resource you want to share with the community. If you choose to implement it as a class-based DSC Resource, then you are potentially limiting the amount of people who can use your resource until WMF 5 becomes a standard.

Since PowerShell v5 is still being developed, some of the following may be resolved by the time it is released:

  • The types that you create in a class are not available outside the module yet. This means if you have several common variables, enums, or methods you have to create them in each class you make. There isn't a global way to address them outside the module they live in.
  • You cannot have class-based and module-based DSC Resources inside the same DSC Resource module.
  • Class-based DSC Resources cannot be pulled from DSC Pull Servers yet. This limits your deployment choices to pushing out these DSC Resources yourself before executing your DSC configuration scripts.
  • You cannot unload classes from a running PowerShell session using Remove-Module, which can cause a failure to reload a class-based DSC Resource when the LCM is set to ForceModuleImport debug mode.
..................Content has been hidden....................

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