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 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.
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.
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.
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.
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.
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 |
---|---|---|
|
|
The property is a key for the DSC Resource and required |
|
|
The property is required |
|
|
The property is read-only and is populated by the |
|
|
This is a configurable property that is not required |
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.
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.
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 } }
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 } } }
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 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:
Remove-Module
, which can cause a failure to reload a class-based DSC Resource when the LCM is set to ForceModuleImport
debug mode.