Scenario: I would like to think I am fairly competent at creating DSC Resources using .MOF files. But with the upcoming release of Windows Management Framework 5.0 we can now write DSC Resources using Classes. I have never done anything with a Class. I am going to attempt to figure out how to write a simple Class based DSC Resource.
I am going to start with this TechNet Article, because it was the first thing that came up when I Googled “Create Class based DSC Resource”.
I am going to create a DSC Resource called MyTestClassResource. The first thing I need to do is create the appropriate folder structure, which I do by running the following two commands:
1 2 |
New-Item -ItemType Directory -Path 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources' New-Item -ItemType Directory -Path 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources\MyTestClassResource' |
Next, I need to create the Module Manifest.
1 2 |
$Path = 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources\MyTestClassResource' New-ModuleManifest -Path $Path\MyTestClassResource.psd1 -Author 'Jacob Benson' -ModuleVersion 1.0 -Description 'Module for Testing Class Based DSC Resources' |
The .PSM1 file is where I define and create my Class based resource.
1 2 |
New-Item -ItemType File -Path $Path\MyTestClassResource.psm1 PSEdit -filenames 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources\MyTestClassResource\MyTestClassResource.psm1' |
Now, let’s get to creating this Resource. I am going to go super simple here and create a Resource that will just ensure that a Folder exists. Yes, I know the File DSC Resource already does this, that’s not the point :). After fiddling around for a little bit here is what my Class DSC Resource “Skeleton” looks like.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
enum Ensure { Absent Present } [DscResource()] class MyTestClassResource{ [DscProperty(Key)] [string]$Path [DscProperty(Mandatory)] [Ensure] $Ensure [DscProperty(Mandatory)] [ValidateSet("Directory","File")] [string]$ItemType #Replaces Get-TargetResource [MyTestClassResource] Get() { } #Replaces Test-TargetResource [bool] Test() { } #Replaces Set-TargetResource [void] Set() { } } |
The rest of this should be pretty straightforward. Right? The first thing I try to do in the Get section is to test and see if the path exists.
1 |
$Item = Test-Path $Path |
This doesn’t work however because “Variable is not assigned in the method”, whatever the hell that means. Looking at the article it uses a $This object (I have no idea if that’s even the right word) with the variables to do things, so let’s try a different tactic.
1 |
$Item = Test-Path $This.Path |
And that works fine, so clearly $This is some special thing I need to be using going forward. This should be fun :). Now that I got that working, the rest of this Method was pretty straight forward.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#Replaces Get-TargetResource [MyTestClassResource] Get() { $Item = Test-Path $This.Path If($Item -eq $True) { $This.Ensure = [Ensure]::Present } Else { $This.Ensure = [Ensure]::Absent } Return $This } |
After that, the Test Method is pretty easy as well. However, I have no idea what is going on with the whole Return -not $Item part, I am just following along from the example and hoping that it works.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#Replaces Test-TargetResource [bool] Test() { $Item = Test-Path $This.Path If($This.Ensure -eq [Ensure]::Present) { Return $Item } Else { Return -not $Item } } |
Onto the Set Method. One thing I noticed when creating this is that I don’t need an Else block with an If statement, which is nice.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#Replaces Set-TargetResource [void] Set() { $Item = Test-Path $This.Path If($This.Ensure -eq [Ensure]::Present) { If(-not $Item) { Write-Verbose "Creating Folder" New-Item -ItemType Directory -Path $This.Path } } #If [Ensure]::Absent Else { If($Item) { Write-Verbose "File exists and should be absent. Deleting file" Remove-Item -Path $This.Path } } } |
With that done I need to re-create the Module Manifest that I made earlier with some important information.
1 2 |
$Path = 'C:\Program Files\WindowsPowerShell\Modules\MyClassResources\MyTestClassResource' New-ModuleManifest -Path $Path\MyTestClassResource.psd1 -Author 'Jacob Benson' -ModuleVersion 1.0 -Description 'Module for Testing Class Based DSC Resources' -RootModule 'MyTestClassResource.psm1' -DscResourcesToExport * |
At this point while trying to run Get-DSCResource I realized that my folder structure I created at the beginning was not correct, because I was used to the way I had been doing it when working with .MOF Files. I actually need only this:
1 2 |
$Path = 'C:\Program Files\WindowsPowerShell\Modules\' New-Item $Path\MyTestClassResource -ItemType Directory |
And then I moved the .psm1 and .psd1 files into that folder. No extra sub-folder required. This is a win in my book.
Now, when I run Get-Module -ListAvailable, the MyTestClassResource Module is listed. However, when I run Get-DSCResource -Module MyTestClassResource I get a lot of nothing. Weird. Next I try to Import-Module -Name MyTestClassResource and I get this giant bundle of joy.
1 2 3 4 5 6 7 8 |
$> Import-Module -Name MyTestClassResource -Verbose VERBOSE: Loading module from path 'C:\Program Files\WindowsPowerShell\Modules\MyTestClassResource\MyTestClassResource.psd1'. Import-Module : The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047) At line:1 char:1 + Import-Module -Name MyTestClassResource -Verbose + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Import-Module], FileLoadException + FullyQualifiedErrorId : System.IO.FileLoadException,Microsoft.PowerShell.Commands.ImportModuleCommand |
Uhhh…What? Thinking quickly I decide that it’s probably not going to work to name everything exactly the same. I rename the MyTestClassResource folder to MyTestClassResources and leave everything else the same. And well, I will spare all the error text but that didn’t work at all either. I am not sure how much time I spent trying to figure out what the hell I was doing wrong, but to say it was an exercise in frustration is a massive understatement. No matter what I did I couldn’t get it to recognize a valid Module, let alone DSC Resource. I lost track of all the troubleshooting that I did but here is what my folder structure looks like now that it is working. Note that I renamed my Class based Resource from MyTestClassResource to MyFolderResource.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$> Get-DscResource -Module MyFolderResource ImplementedAs Name ModuleName Version Properties ------------- ---- ---------- ------- ---------- PowerShell MyFolderResource MyFolderResource 1.0 {Ensure, Path, DependsOn, PsDscRunAsCredential} $> Get-DscResource -Module MyFolderResource -Syntax MyFolderResource [String] #ResourceName { Ensure = [string]{ Absent | Present } Path = [string] [DependsOn = [string[]]] [PsDscRunAsCredential = [PSCredential]] } |
Now, let’s test a Configuration!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
Configuration TestClass { Import-DSCResource -ModuleName MyFolderResource MyFolderResource Test { Ensure = "Present" Path = "C:\Scripts\Blah" } } TestClass -OutputPath C:\Scripts\TestClass Start-DSCConfiguration -Wait -Force -Verbose -Path C:\Scripts\TestClass $> Start-DSCConfiguration -Wait -Force -Verbose -Path C:\Scripts\TestClass VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windo ws/DesiredStateConfiguration'. VERBOSE: An LCM method call arrived from computer VALHALLA with user sid S-1-5-21-3449200860-990092898-4058043841-1001. VERBOSE: [VALHALLA]: LCM: [ Start Set ] VERBOSE: [VALHALLA]: LCM: [ Start Resource ] [[MyFolderResource]Test] VERBOSE: [VALHALLA]: LCM: [ Start Test ] [[MyFolderResource]Test] VERBOSE: [VALHALLA]: LCM: [ End Test ] [[MyFolderResource]Test] in 0.2180 seconds. VERBOSE: [VALHALLA]: LCM: [ Start Set ] [[MyFolderResource]Test] VERBOSE: [VALHALLA]: [[MyFolderResource]Test] Exporting function 'Get-FileHash'. VERBOSE: [VALHALLA]: [[MyFolderResource]Test] Exporting function 'Get-SerializedCommand'. VERBOSE: [VALHALLA]: [[MyFolderResource]Test] Exporting function 'New-TemporaryFile'. VERBOSE: [VALHALLA]: [[MyFolderResource]Test] Creating Folder VERBOSE: [VALHALLA]: LCM: [ End Set ] [[MyFolderResource]Test] in 0.1970 seconds. VERBOSE: [VALHALLA]: LCM: [ End Resource ] [[MyFolderResource]Test] VERBOSE: [VALHALLA]: LCM: [ End Set ] VERBOSE: [VALHALLA]: LCM: [ End Set ] in 0.8620 seconds. VERBOSE: Operation 'Invoke CimMethod' complete. VERBOSE: Time taken for configuration job to complete is 1.004 seconds |
And just to make sure it’s working, let’s set it to Absent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windo ws/DesiredStateConfiguration'. VERBOSE: An LCM method call arrived from computer VALHALLA with user sid S-1-5-21-3449200860-990092898-4058043841-1001. VERBOSE: [VALHALLA]: LCM: [ Start Set ] VERBOSE: [VALHALLA]: LCM: [ Start Resource ] [[MyFolderResource]Test] VERBOSE: [VALHALLA]: LCM: [ Start Test ] [[MyFolderResource]Test] VERBOSE: [VALHALLA]: LCM: [ End Test ] [[MyFolderResource]Test] in 0.1840 seconds. VERBOSE: [VALHALLA]: LCM: [ Start Set ] [[MyFolderResource]Test] VERBOSE: [VALHALLA]: [[MyFolderResource]Test] Exporting function 'Get-FileHash'. VERBOSE: [VALHALLA]: [[MyFolderResource]Test] Exporting function 'Get-SerializedCommand'. VERBOSE: [VALHALLA]: [[MyFolderResource]Test] Exporting function 'New-TemporaryFile'. VERBOSE: [VALHALLA]: [[MyFolderResource]Test] File exists and should be absent. Deleting file VERBOSE: [VALHALLA]: LCM: [ End Set ] [[MyFolderResource]Test] in 0.1590 seconds. VERBOSE: [VALHALLA]: LCM: [ End Resource ] [[MyFolderResource]Test] VERBOSE: [VALHALLA]: LCM: [ End Set ] VERBOSE: [VALHALLA]: LCM: [ End Set ] in 0.6720 seconds. VERBOSE: Operation 'Invoke CimMethod' complete. VERBOSE: Time taken for configuration job to complete is 0.771 seconds |