My first DSC class resource - Some questions

So I’ve just written my first DSC class resource (skipped cmdlet based resources). I’m hoping someone can give me some guidance on a couple of questions.

Question 1: When testing to see if something has been set correctly in Test() and using an optional property I’m checking to see if the property was defined before making the test. Is there a better way to implement this?

if ($this.VendorClass) {
    if ($OptionDefinition.VendorClass -ne $this.VendorClass)
        $return = $False

Question 2: I’m using the DHCPServer module in the resource, when Test() runs is spams out a TON of verbose from DHCPServer (basically it’s doing a Import-Module DHCPServer -verbose in the background). How do I stop all this verbose traffic? When I see other DSC resource use modules I don’t see verbose from the modules they use.

Question 3: Get() is supposed to return an instance of the class. Which of the following methods for doing that is correct?

Method 1

$MyInstance = [MyInstance]::new()
$MyInstance.Name = 'blah'
return $MyInstance

Method 2 (MSFT use this method

return $this

Question 4: When the LCM runs the config ‘Start Set’ runs first with no information. Is that correct?

VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer LABSERVER01 with user sid S-1-5-21-2199847848-3872042683-2133798733-1134.
VERBOSE: [S12AUMDC01TST05]: LCM:  [ Start  Set      ]
VERBOSE: [S12AUMDC01TST05]:                            [DSCEngine] Importing the module C:\Program Files\WindowsPowerShell\Modules\MyInstance\MyInstance.psd1 in force mode.
VERBOSE: [S12AUMDC01TST05]: LCM:  [ Start  Resource ]  [[MyInstance]Test1]
VERBOSE: [S12AUMDC01TST05]: LCM:  [ Start  Test     ] 

Question 1:
From what I understand, the general design principals around class based resources are to use them as an interface to a module that would do the heavy lifting, keeping the method code light and the implementation abstracted (This also aids unit testing of your implementation). You might want to think about moving some of the logic to cmdlets opposed to method code, potentially building up a pipeline that either start with $this, or a cmdlet that actually obtains the Vendor Class


Test() {

	$TestResult = Get-VendorClass | Test-VendorClass
	return $TestResult

Question 2:
Is the module a listed as a RequiredModule in the ModuleManifest? Potentially when your running the cmdlets in the resource its doing the import, as its not already been imported at a higher scope.

Question 3:
Option 2 definitely, the Get method is already working on an instantiated object (its self) - option 1 would mean creating an entire new object.

Question 4:
I’d need to do some testing, but I believe that Start Set is the start of a configuration run, the resource is starting at LCM [ Start Resource ], then the test executes as you would expect first.

Hi Luke, thank you so much for your response. Here are my comments…

Question 1: Thanks for the heads up.

Question 2: I managed to resolve this by making the DHCPServer module load before the class with -Verbose:$False. I’ll try RequiredModule as you suggested, tomorrow.

Question 3: I actually found method 2 breaks DSC because $this holds what your desired state should be. For instance if you wanted to see if the desired state is Ensure, you would check $This.Ensure. If you’re modifying $this in Test() then your changing what the intended desired state should be and so when you check $this in Set() and Test() the logic goes out the door because the intended desired state has changed during runtime. Ideally you want to leave $this alone because it’s the only place that holds what your desired state should be, unless I’m mistaken? $this acts as a global variable and it should be treated like one. Would love to hear your thoughts…

Hi Zuldan,

The write properties on $this are indeed what you expect the desired state to be so changing this could definitely cause problems if there is some odd code going on, but personally I don’t believe this situation would ever occur and if it does, personally I believe the framework is being bent into a way it shouldn’t because it wouldn’t make sense.

From what I’ve observed no information in the Test that is set on $this is ever passed to the next stage because the Set (and similarly the Get) execute in their own runspace entirely separate. No $global scope variable would be able to get to the next stage and no information can be passed within powershell without deserialising it to disk or queue. There are technical reasons for this - The LCM allow Set / Test to decrypt PSCredential objects where as the Get cant to avoid any credential objects leaking out by accident.

Personally, i’d be interested in knowing why you’re changing properties on $this in the test, as from my experience the write properties on $this are used to just check the state of the machine and determine if its compliant then return true or false to tell the engine if set should run - These methods are intended to have a very small chunk of responsibility so it can be kept simple.

Hi Luke,

Question 2: Your RequiredModule solution worked. Thank you!

Question 4: My apologies. Setting $this in Test() was a typo. It was supposed to be Get(). When I set $this.Ensure in Get(), it changed $this.Ensure in Test(). I’ll have to do some more testing and replicate it again.

Would be great if you can replicate it, I’ve been testing myself and have been unable to get the same behaviour using ps 5.1 on 2016tp.

I’ll try and get some traces up to show my examples.

Could you also tell me if you can enter in debug session with a DSC class and 5.1. It appears to be broken.

So if I run the following (when PS tells you too)…

Enter-PSHostProcess -Id xxx -AppDomainName DscPsPluginWkr_AppDomain
Debug-Runspace -Id x

…it bombs out on Debug-Runspace (can’t remember the error message). It works perfectly in 5.0 though.

If you are arent able to debug DSC classes either then I’ll create a UserVoice.

Have no problem debugging on 5.1, see the below gist file list that has me debugging on the same version - I have a number of the transcripts I’ve worked with for my examples. Part to take away is the Caller, Get and Test debugging transcripts. In the GET i changed the Ensure property to Absent, and its returned to the Caller showing Absent as it was changed in that runspace, yet when I ran and debugged the test with Test-DscConfiguration following that the property is back to Present. This is due to the way the DSC engine uses MOF’s, and will only use the stored serialized data from the mof as parameters to start the Get/Set/Test.

Unsure why your seeing an error entering, whats the exception you’re seeing?

Accidental double post :frowning:

Luke that’s making sense now. Thanks for the transcript. Going to update my code tomorrow. Maybe I was doing something weird and thought Ensure had changed.

As for the debugging. I’m using 5.1 (upgraded from 5.0) on 2012 R2 so maybe it’s only broken on that OS? Unless it’s just broken on my environment (which is what I’m leaning towards). Going to give it another crack tomorrow.

Hi Luke, I updated my code so that Get() returns a modified $this and the class completely broke (including the Pester tests) so I’ve reverted back to creating my own instance of the class and returning that instead.

I tried to find some other real world examples of someones code returning $this in Get() but only found people creating their own instance as well. See below; Now I’m totally confused.

As for debugging on 5.1, it works for the ‘most’ part. However, after 10 to 15 debugging sessions (on the same class) and with WmiPrvSE.exe reaching over 110,000K in memory (which isn’t very much but I guess it starts at around 10,000K), I get the following error and cannot debug any further. Once I kill the WmiPrvSE.exe process I can then start debugging again. I think I might report it on UserVoice.

Value cannot be null.
Parameter name: No Runspace was found.
    + CategoryInfo          : InvalidOperation: (Microsoft.Power...RunspaceCommand:DebugRunspaceCommand) [Debug-Runspace], PSArgumentNullException
    + FullyQualifiedErrorId : DebugRunspaceNoRunspaceFound,Microsoft.PowerShell.Commands.DebugRunspaceCommand