The Collection

A collection of useful information.

PowerShell: DSC Custom Resource

IMO the DSC information out there right now is kind of all over the place, and worse some of it is outdated, so I'm going to collect a few guides here, starting a bit out of order with a guide on how to write your own custom DSC resource. Later I'll put one together for configuring a DSC Pull server and then one on how to configure a client for DSC Pull operations.

First things first, you need this: xDscResourceDesigner

We wont be using any of the other functionality of it, but it at the very least is handy for creating the initial structure. Extract it like it says but most likely depending on your setup you are going to need to teomporarily run with an Unrestricted ExecutionPolicy. I couldn't unflag it to save my life and frankly didn't spend much time on it, remember to set your policy back after using it and there are no worries.

Anyway, the first thing you need to know is, what do you need to know? We are going to create something really simple here, a DSC Resource to make sure a file exists and set its contents. So we need a Path and Contents, then we need the mandatory Ensure variable, so our first bit of code is going to look like this:

PasteBin

What is essentially going on here is we are setting up our variables and then building the folder structure and MOF for our custom resource. Saving it all to our PSModules path so once you run this command you should be able to Get-DscResource and see zTest at the bottom of the list.

Now we have a script to modify, this script is located in the DSCResources sub-folder in the path we just specified. There are a couple things to know about this:

The Test Phase

This is where LCM looks to validate the state of the asset you are trying to configure. In our case, does this file exist? And what are its contents? So the content of your Test-TargetResource script will probably look a little something like:

if((Test-Path $Path) -eq $true){ if((Get-Content $Path) -eq $Content){ $return = $true }else{ $return = $false }else{ $return = $false }

That is pretty messy and all one line but you get the idea, there is no logic built in, you need to provide all the logic. Whether that be ensuring that you don't just assume that because the file exists it must have the right contents, or the logic to ensure that if $Ensure is set to "Absent" that you are removing the file thoroughly, handling errors, not outputting to host, etc. etc.

So assuming you do not have the file already and we run this resource, it will run through the Test-TargetResource and return $false, at which point if you set Ensure="Absent" it will be done, if you set it to "Present" it will move on to Set-TargetResource. Which will probably look something like this:

if($Ensure -eq "Present") {
	New-Item -Path $Path -ItemType File -Force
	Set-Content -Path $Path -Value $Content
}elseif($Ensure -eq "Absent") {
	Remove-Item -Path $Path -Force -Confirm:$false
}

Again, this is quick and dirty and not meant to represent the most robust function possible for creating/populating a file or deleting it thoroughly. Liberal use of try/catch and thinking about ways it could go wrong (including doing a little of your own validation to make sure what you just did took hold) are recommended.

The Test function MUST return $true or $false, Set does not need to (should not?) return anything at all, which leaves: Get-TargetResource

For now I'm not going to go too far into this, I'll just say that in reality this appears to be an informational function that allows you to pull information about the asset in question without actually doing anything. It MUST return a hastable of information about the object (usually the same information, sans Ensure, that you provide to the script). i.e.

$returnVal = @{
	Path = $Path
	Content = (Get-Content -Path $Path)
	Ensure = $Ensure
}

Again how strictly this is enforced and how much is really needed...in this example I would almost say not at all. You could just as easily replace $Path with (Test-Path -Path $Path) and drop Ensure entirely I think. I haven't tested the boundaries of how strictly you have to adhere to input->outputin this case.

Once all of this is done you should be able to do the following and get a usable mof ready to be executed:

Configuration Test {
	Import-DscResource -Module zTest
	Node "localhost" {
		zTest Testing {
			Ensure = "Present"
			Path = "C:\test.txt"
			Content = "Hello (automated) World!"
		}
	}
}

$output = "C:\\"
Test -OutputPath $output
Start-DscConfiguration -Verbose -Wait -Force -Path $output

Please note that the OutputPath argument isn't required, without it the script will just spit out a mof in, well in this case it will create a folder called Test in whatever folder you are currently in and put it in there. I like to specify it so I know where my mof's are. Once you call it with that final command you should see LCM spit out a bunch of information regarding the process and any errors will show up there as well as in the Event Viewer.

Please note that a well written DSC Resource should be able to be run a million times and only touch something if it isn't in compliance. Meaning that if the script doesn't detect the file is missing or the contents are different from what they should be, it should effectively do nothing at all. It shouldn't set it anyway just to be sure, it shouldn't assume, it shouldn't cut corners, or hope and pray. The entire point here is to ensure consistent state accross an environment. The better your script is, the more reliable, and resilient, forward thinking and robust, the better your resource will be.

In the case of this Resource, if I planned on taking it farther I would probably make sure I had rights to the file before messing with it, include a try/catch around file creation and content placement, I would check the file existed once more with a fresh piece of memory (if you end up setting the content to a variable) and validate the contents afterwards as well. You could build in the option to allow forcing a specific encoding type to convert between UTF-32 and UTF-8 or ASCII if you wanted, provide options for Base64 input encoding (in case you wanted to handle script strings for instance, or you could make sure to escape and trim as needed) etc. etc.

The point being, it is easy to write something quick that will rather blindly carry out an action. But in the long run you need something that is going to be feature rich and robust to match.

And don't forget, if you don't copy the folder structure to the client machines, they wont have access to your Resource. The path we created this in on our workstation is the same path it needs to live on every other machine, and once it does, they too will be able to use the Resource.