Windows Admin Center (WAC) is a locally deployed, browser-based app for managing servers, clusters, hyper-converged infrastructure, and Windows 10 PCs. It was previously known as Project Honolulu.
WAC really shines when being used to manage headless Windows Servers (e.g. Windows Server Core). The benefits of deploying Windows Server Core are huge, but it can be a bit daunting to system administrators that have only used the Windows GUI experience to manage servers.
It is pretty easy to install WAC, but if you want to install it with PowerShell DSC, then here is a config for you to use:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The config is parameterized and supports specifying the port for the WAC to listen on and using either a self-signed certificate or a local machine certificate in the by specifying a thumbprint.
To apply the DSC using a self-signed certificate and on the default port of 6516, run the following in an Administrator PowerShell console:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
You can run this on a Windows Server Core machine by logging in and typing powershell to start a PowerShell console, then entering the commands above.
To apply the DSC configuration specifying a certificate with a thumbprint from the local machine store and on Port 4000, run these commands instead:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Windows Server 2016 is now GA and it contains some pretty exciting stuff. Chief among them for me is support for containers by way of Docker. So, one of the first things I did was start installing Windows Server 2016 VM’s (Server Core and Nano Server naturally) and installing Docker on them so I could begin experimenting with Docker Swarms and other cool stuff.
Edit: If you’re looking for a DSC configuration for setting up Docker on a Windows 10 Anniversary Edition machine, see the Windows 10 AE section below.
So, what I did was put together a basic DSC config that I could load into a DSC Pull Server and build out lots of Docker nodes quickly and easily. This worked really nicely for me to build out lots of Windows Server 2016 Container hosts in very short order:
If you don’t have a DSC Pull server or you just want a simple script that you can use to quickly configure a Windows Server 2016 (Core or Core with GUI only) then read on.
Note: This script and process is really just an example of how you can configure Docker Container hosts with DSC. In a real production environment you would probably want to use a DSC Pull Server.
Get it Done
Edit: After a suggestion from Michael Friis (@friism) I have uploaded the script to the PowerShell Gallery and provided a simplified method of installation. The steps could be simplified even further into a single line, but I’ve kept them separate to show the process.
Using PowerShell Gallery
On a Windows Server 2016 Server Core or Windows Server 2016 Server Core with GUI server:
Log on as a user with Local Administrator privileges.
Start an Administrator PowerShell console – if you’re using Server Core just enter PowerShell at the command prompt:
Install the Install-DockerOnWS2016UsingDSC.ps1 script from the PowerShell Gallery using this command:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
You may be asked to confirm installation of these modules, answer yes to any confirmations.
Run the Install-DockerOnWS2016UsingDSC.ps1 script using:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The script will run and reboot the server once. Not long after the reboot the Docker service will start up and you can get working with containers:
You’re now ready to start working with Containers.
The Older Method (without PowerShell Gallery)
On a Windows Server 2016 Server Core or Windows Server 2016 Server Core with GUI server:
Log on as a user with Local Administrator privileges.
Start an Administrator PowerShell console – if you’re using Server Core just enter PowerShell at the command prompt:
Install the DSC Resources required for the DSC configuration by executing these commands:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
You may be asked to confirm installation of these modules, answer yes to any confirmations.
Download the Docker installation DSC script by executing this command:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Run the Docker installation DSC script by executing this command:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The script will run and reboot the server once. Not long after the reboot the Docker service will start up and you can get working with containers:
You’re now ready to start working with Containers.
What the Script Does
In case you’re interested in what the script actually contains, here are the components:
ConfigurationContainerHostDsc – the DSC configuration that configures the node as a Docker Container host.
ConfigurationConfigureLCM – the LCM meta configuration that sets Push Mode, allows the LCM to reboot the node if required and configures ApplyAndAutoCorrect mode.
ConfigData – a ConfigData object that contains the list of node names to apply this DSC Configuration to – in this case LocalHost.
Set-DscLocalConfigurationManager – this applies the compiled LCM meta configuration MOF file to LocalHost to configure the LCM.
ContainerHostDsc – the call to the ConfigurationContainerHostDsc to compile the DSC MOF file.
Start-DSCConfiguration – this command starts the LCM applying the DSC MOF file produces by the ContainerHostDsc.
The complete script can be found here. Feel free to use this code in anyway that makes sense to you.
What About Windows 10 AE?
If you’re looking for a DSC configuration that does the same thing for Windows 10 Anniversary edition, Ben Gelens (@bgelens) has written an awesome DSC config that will do the trick. Check it out here.
I’ve had one resource already accepted (xDFS) into the DSC Community Resource kit, but this was before the High Quality Resource Module (HQRM) guidelines became available. The HQRM guidelines are a set of standards that DSC modules must meet and maintain to be considered a High Quality Resource Module. Once they meet these requirements they may be eligible to have the ‘x’ moniker removed with ‘Dsc‘ being added to the name.
More information: If you want to read a bit more about the HQRM standards, you can find the HQRM Guidelines here.
Any modules being submitted for inclusion into the DSC Community Resource kit will be expected to meet the HQRM standards. The process of acceptance requires three reviewers from the Microsoft DSC team to review the module.
I thought it might be helpful to anyone else who might want to submit a DSC Resource into the DSC Community Resource kit to get a list of issues the reviewers found with my submissions. This might allow you to fix up your modules before the review process – which will help the reviewers out (they hate having to be critical of your code as much as you do). This enables the submission process to go much faster as well.
More information: If you want to read more about the submission process, you can find the documentation here.
I’ll keep this post updated with any new issues the reviewers pick up. Feel free to ask for clarifications on the issues.
So here is my list of what I have done wrong (so far):
Missing Get-Help Documentation
Every function (public or private) within the DSC resource module must contain a standard help block containing at least a .SYNOPSIS and .PARAMETER block:
This will get rejected:
This is good:
Examples Missing Explanation
All examples in the Examples folder and the Readme.md must contain an explanation of what the example will do.
This is bad:
This is good:
Old or Incorrect Unit/Integration Test Headers
There is a standard method of unit and integration testing DSC Resources. Your DSC resources should use these methods where ever possible. Any tests should therefore be based on the latest unit test templates and integration test templates. You should therefore ensure your tests are based on the latest practices and contain the latest header.
This is probably the hardest thing to get right if you’re not paying close attention to the current DSC community best practices around testing. So feel free to ask me for help.
This is bad:
This is good:
Incorrect Capitalization of Local Variables
Local variables must start with a lower case letter. I needed to correct this on several occasions.
Note: this is for local variables. Parameter names should start with Uppercase.
This is bad:
This is good:
Spaces around = in Localization Files
In any localization files you should make sure there is a space on either side of the = sign. This greatly improves message readability.
This is bad:
This is good:
Missing code of Conduct in Readme.md
All modules that are part of the DSC Resource Kit must contain this message in the Readme.md:
All strings in localization files should be indented.
This is bad:
This is good:
Final Words
There were some other issues raised which I will also document, however I am still in discussion with the DSC team over the best methods to use to solve the issues (specifically the use of InModuleScope in unit tests).
The main thing you can do to help speed this process up and reduce the load on the reviewers however is to implement all the best practices and guidelines listed.
The latest DSC Resource Kit (all your favorite DSC Resources in one handy pack) is available now. It is one mighty release with all sorts of awesomeness included! I strongly recommend picking it up if you’re doing DSC automation, as it has something for everyone.
The xDFS DSC resource module has been officially released to the PowerShell Gallery thanks to the awesome review efforts of the Microsoft PowerShell Team. The cDFS DSC Resource has now been unlisted from the PowerShell Gallery. So now is the time to update any DSC configuration scripts to use xDFS.
Important: There were some minor changes to xDFS when it was converted from cDFS. For information on what you’ll need to change to convert to xDFS see my earlier post.
Just a Friday afternoon heads up – if you’re using the cDFS DSC Resource I created to manage Windows Server Distributed File System (Replication and Namespaces), it has now been accepted into the PowerShell Community resources and will be under the control of the PowerShell Team.
This means that the GitHub source code repository will be moving over to the PowerShell organization in the next few days. This also means that any future releases of this resource module won’t be provided by me as cDFS, but will be released by the PowerShell team as xDFS.
So I recommend that when this happens you switch over to using the xDFS resource. I will put another post up here when the change over officially occurs. The first official release version under the new xDFS name will be 3.0.0.x. I won’t make any further changes or bug fixes to the cDFS resources.
It is also worth noting that as part of this move some minor changes were made to the DSC Resource modules. These are breaking changes and you will most likely need to update any DSC Configurations depending on this, but you would have to do this anyway because of the name change.
The changes are:
Resource xDFSRepGroup renamed to xDFSReplicationGroup
Resource xDFSRepGroupConnection renamed to xDFSReplicationGroupConnection
Resource xDFSRepGroupFolder renamed to xDFSReplicationGroupFolder
Resource xDFSRepGroupMembership renamed to xDFSReplicationGroupMembership
xDFSReplicationGroupConnection:
Changed DisableConnection parameter to EnsureEnabled.
Changed DisableRDC parameter to EnsureRDCEnabled.
These changes should only require minor changes to your configuration scripts to implement.
In my previous post I showed how to create a PowerShell script that would install a Jenkins CI Master server onto a Windows Server Core installation. The obvious next step for such a script was to convert it into a DSC configuration file.
In this post I’m assuming WMF 5.0 is installed onto the server that will be converted into a Jenkins Master. You could manage this without WMF 5.0, but you’d need to manually install the DSC Resource modules that the configuration will use.
Once again, the full DSC Configuration script can be found at the end of the post.
Requirements
You’ll need:
A physical or virtual machine running Windows Server 2012 R2 Core (or Full) – it should be a completely clean install with WMF 5.0 installed on it.
An administrator login to the server.
An internet connection to the server.
Resource Modules
This DSC Configuration requires the use of three DSC Resources:
cChoco – this community resource is used to install Chocolatey and Jenkins.
xNetworking – this resource is used to configure the networking on the server if required.
PSDesiredStateConfiguration – this resource comes with PowerShell by default and is used to provide the Script resource.
The easiest way to install these resource modules is by executing these commands on the Jenkins server:
# Make sure the DSC Resource modules are downloaded
Install-Module -Name cChoco -Force
Install-Module -Name xNetworking -Force
However, if you’re using a Pull server or compiling the DSC MOF on a development machine (rather than the Jenkins node) you would need to use other methods of ensuring the modules are available.
The Configuration Components
The DSC Configuration needs to do the following things:
Configure Networking (optional)
Install .NET 3.5 Framework
Install Chocolatey
Install JDK 8
Install Jenkins
Configure Jenkins Port (optional)
Configure Networking
I like to use the xNetwoking DSC resource to configure the IPv4 and IPv6 settings on the Network adapter to have a static configuration. However, you won’t need to do this if you’re using DHCP or manual configuration. Note, in my case my adapter was called “Ethernet”.
Jenkins requires the .NET 3.5 Framework, so I’m going to use the WindowsFeature DSC Resource to install it:
WindowsFeature NetFrameworkCore
{
Ensure = "Present"
Name = "NET-Framework-Core"
}
Install Chocolatey
Next up, I’m going to use the cChocoInstaller resource in the cChoco resource module (available on PowerShell Gallery here) to install the Chocolatey package manager:
The last step of the configuration is optional. By default Jenkins is configured to listen on port 8080, however I want to change it to 80. So this next part uses the Script resource to change the “–httpPort” setting in the Jenkins.xml file. I use Regex to do this:
# Set the Jenkins Port
Script SetJenkinsPort
{
SetScript = {
Write-Verbose -Verbose "Setting Jenkins Port to $Using:JenkinsPort"
$Config = Get-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
$NewConfig = $Config `
-replace '--httpPort=[0-9]*\s',"--httpPort=$Using:JenkinsPort "
Set-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml" `
-Value $NewConfig `
-Force
Write-Verbose -Verbose "Restarting Jenkins"
Restart-Service `
-Name Jenkins
}
GetScript = {
$Config = Get-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
$Matches = @([regex]::matches($Config, "--httpPort=([0-9]*)\s", 'IgnoreCase'))
$CurrentPort = $Matches.Groups[1].Value
Return @{
'JenkinsPort' = $CurrentPort
}
}
TestScript = {
$Config = Get-Content `
-Path "${ENV:ProgramFiles(x86)}\Jenkins\Jenkins.xml"
$Matches = @([regex]::matches($Config, "--httpPort=([0-9]*)\s", 'IgnoreCase'))
$CurrentPort = $Matches.Groups[1].Value
If ($Using:JenkinsPort -ne $CurrentPort) {
# Jenkins port must be changed
Return $False
}
# Jenkins is already on correct port
Return $True
}
DependsOn = "[cChocoPackageInstaller]installJenkins"
}
Create the MOF
The final thing to do is download the cChoco and xNetworking DSC Resources,create the MOF and then ask the LCM to apply it:
Here is the complete DSC Configuration file. You just need to copy it to the Server and run it. It will compile the configuration into a MOF and tell the LCM to apply it. Just remember to ensure required DSC Resource modules are installed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Several months back I created a DSC Resource for configuring iSCSI Server Targets (including Virtual Disks) as well as iSCSI Initiators using Desired State Configuration (DSC). I created this for several reasons:
I needed a way for LabBuilder to automatically build Scale-Out File Servers (with CSVs).
This weekend I decided to add iSNS Server support to the resource – for both the ciSCSIServerTarget and ciSCSIInitiator resources. So with that feature added I thought it might be a good opportunity for me to write a quick blog post on how to use these DSC Resources.
For those of you using Windows Management Framework 5.0 (or have the PowerShellGet module installed) you can just use the command:
Install-Module -Name ciSCSI
If you don’t have Windows Management Framework 5.0 (and don’t have the PowerShellGet module installed) you will need to download and install the resource from the GitHub Repository.
Using the Resource
If you’d rather just jump right into the resource documentation and examples you can find it here. Otherwise, read on and I’ll cover this resource to configure both an iSCSI Server Target and an iSCSI Initiator. I’ll also show how to register iSCSI Server Targets and Initiators with an iSNS Server.
Important: Although the ciSCSI DSC Resource will work on Windows Management Framework 4.0, these examples require the use of the WaitForAny DSC Resource, which is only available in Windows Management Framework 5.0. This resource is used to ensure that the iSCSI Server Target has been created before trying to connect any iSCSI Initiators to it. The resource could be omitted, but errors will reported by the LCM on the iSCSI Initiator computers if the iSCSI Server Target is not available before the iSCSI Initiator DSC MOF is applied.
The Example Environment
In this example, the DSC Configurations that are being created will refer to the following servers:
FS1.CONTOSO.COM – this is the file server that will contain the iSCSI Virtual Disks and iSCSI ServerTarget.
CLUS1.CONTOSO.COM,CLUS2.CONTOSO.COM,CLUS3.CONTOSO.COM – these are the Windows Server 2012 R2 (or Windows Server 2016) Cluster Server nodes that will be connecting to the iSCSI Server Target.
ISNS1.CONTOSO.COM – this is a server with the iSNS Server Windows Feature installed on it. The iSNSdefault domain has been configured on this server already.
The DSC configurations that will be created will create four 128GB dynamic iSCSI Virtual Disks on the D:\ drive of FS1.CONTOSO.COM. An iSCSI Server Target called FS1-Server-Target will be created and the four iSCSIVirtual Disks attached to it.
Configuring the iSCSI Server Target
A DSC configuration that creates an iSCSI Server Target requires the following steps to be performed in the DSC Resource:
Install the iSCSI Target Server Windows Feature (FS-iSCSITarget-Server).
Initialize and physical disks that will be used to store the iSCSI Virtual Disks (optional).
Create the iSCSI Virtual Disks that will be used by the iSCSI Server Target.
Create the iSCSI Server Target and optionally register it with an iSNS Server.
Here is the DSC Configuration:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Important: Note that the TargetName is set to ‘FS1-Server-Target‘, which will automatically configure the Target IQN to ‘iqn.1991-05.com.microsoft:FS1-FS1-Server-Target-Target’. This is because the Microsoft iSCSI Server Target cmdlets automatically name the Server Target for you using the following format:
This is very important to remember because the iSCSI Initiators use this string to identify the Server Target to connect to.
The rest of the components of this DSC Configuration are self-explanatory as long as you keep in mind the example environment that is being configured.
Configuring the iSCSI Initiator
A DSC configuration for each of the iSCSI Initiators that will connect to the iSCSI Server Target requires the following steps to be performed in the DSC Resource:
Start the Microsoft iSCSI Initiator Service service (MSiSCSI).
Use the WaitForAny WMF 5.0 DSC Resource to wait for the iSCSI Server Target to be created (optional).
Connect the iSCSI Initiator to the iSCSI Server Target and optionally register it with an iSNS Server.
Here is the DSC Configuration for CLUS1.CONTOSO.COM (the configuration for the other nodes would be similar except with different InitiatorPortalAddress values):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Important: We need to make sure the NodeAddress is set to the the Target IQN from the iSCSI Server Target – in this case ‘iqn.1991-05.com.microsoft:FS1-FS1-Server-Target-Target’.
It is also recommended that you use IP Addresses for the TargetPortalAddress and InitiatorPortalAddress parameters rather than server names, as this will force the iSCSI traffic to use the appropriate network adapter.
The components of this DSC Configuration are self-explanatory as long as you keep in mind the example environment that is being configured.
iSNS Server Configuration
There are a few things to keep in mind when you have your iSCSI DSC Configurations registering with an iSNS Server:
The Default Domain on the iSNS Server should have been created.
If the iSNS Server is not available or contactable by the iSCSI Server Target or Initiator when the DSC Configuration is applied the DSC configuration will not throw an error, but the iSNS Server Address will not be set. However, next time the DSC configuration is applied by the LCM it will try again (and again the next time etc).
Using iSNS Server is completely optional and is mostly used in larger environments with more than twenty iSCSI Server Targets and where the Initiators will be connected to the iSCSI Server Targets manually or where DSC can’t be used on the iSCSI Server Targets.
That is all there is to using this resource to configure a Windows Server 2012 iSCSI SAN using DSC.
Note: I have submitted this DSC Resource to be included in the Microsoft Community DSC Resources project. If it is accepted then the name of the DSC Resource will change from ciSCSI to iSCSI. The resource hasn’t yet been reviewed and I’m not aware of an ETA for it. The old ‘c’ and ‘x’ nomenclature used by DSC Resources is being phased out.
If you need some additional guidance or other specific examples, please feel free to drop a comment on this blog post (or the GitHub repository) and I’ll do my best to help you out.
The purpose of this series of articles is to try and document a few of the lessons I learned while releasing new DSC resources as well as contributing to the existing Microsoft Community DSC resources. These articles are not intended to tell you how to write DSC resources from a programming perspective, but to give you some ideas on what might be expected of a DSC resource you’re releasing to the public. For example, unit and integration tests (don’t worry if you aren’t familiar with those terms).
These articles are also not intended to tell you what you must do to release your resource, but more document what will help your resource be easier to use and extend by other people. Some of these these things are obvious for people who have come from the development community, but may be quite new to operations people.
If you missed any previous articles you can find them here:
In the last couple of articles I covered the importance of automated testing with unit testing in particular. I had covered creating new unit tests using the unittest templates that are now available here. I also covered how to complete the Pester Test Initialization and the Get-TargetResource,Set-TargetResource and Test-TargetResource function areas of the unit test.
Integration Testing
Integration testing is a great way of catching many errors that can’t be easily picked up by Unit testing. It effectively tests your DSC Resource by actually using it in a DSC configuration file and applying it to a computer and checking the results. So this is as close to real-life testing as you can get.
Integration testing of a PowerShell DSC resource should be performed after unit testing. When a PowerShell DSC Resource is integration tested the following process occurs:
A DSC configuration file using the DSC resource to be integration tested is compiled into a MOF.
The MOF file is applied to the test machine.
The parameters current DSC Configuration of this DSC Resource on the test machine is obtained.
The parameters of the current DSC Configuration are compared with what was set in the DSC configuration file in step 1.
Just like unit testing we use Pester to test the above steps and ensure that errors don’t occur and the output is as expected.
Sometimes Integration Tests are not Possible
Integration testing is not always possible on a resource. Some resources may rely on external servers being available or they might be destructive to the machine performing the tests.
For example, integration tests could not be implemented for the MSFT_xIPAddress resource in the xNetworking DSC Resource module because it would have caused the network to disconnect during testing which would have resulted in a failure of the AppVeyor CI machine running the tests.
But, if there is a reasonable way of implementing integration tests for a resource in a non-destructive manor, then I’d strongly recommend it – especially as it is usually really easy.
Don’t Be Destructive!
Unlike unit testing, integration testing actually changes configuration on the machine performing the tests. If you’re using a continuous integration service like AppVeyor to perform your tests then this isn’t such a problem as the test machine is “destroyed” after your tests are run.
However, many people also run any integration tests on their local machines before committing code, therefore, your integration tests should always leave the machine in the state that it was before running them. This means that any changes that will be made applying the integration tests should be undone at the completion of your integration tests script.
Integration Test Files
Integration tests for a DSC resource actually consist of two different files:
*.config.ps1 – The DSC Configuration file that will use the DSC Resource being tested.
*.Integration.Tests.ps1 – The Integration Test script file containing the Pester tests.
These files should be stored in the Tests\Integration folder in the DSC Resource module:
You must also ensure that the names of these files exactly matches the name of the resource itself. For example, if your DSC Resource is called BMD_MyResource then these files must be called:
BMD_MyResource.config.ps1
BMD_MyResource.Integration.Tests.ps1
Creating a New Integration Test
Luckily, a good amount of the work in implementing integration tests is already done for you. Like unit tests, templates for the two integration files are available in the DscResources repository in GitHub:
You need to copy the integration test template files and rename them to match your DSC Resource.
The easiest way to do this is to clone the repository containing the test template files and copy the integration_template.ps1 and integration_config_template.ps1 files to your Tests/Integration folder:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
You’ll now have two new integration test files that you can open in your PowerShell editor of choice.
Modifying the Config File
The first file I usually edit is the *.config.ps1 file:
Next, you’ll want to change any <ResourceName> occurrences in this file to the name of your resource. I also like to remove the #TODO bits at the same time so I know what I’ve completed:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Next, we need to configure the config file with the parameters we want to use as tests of the resource.
The best way of doing this is actually to create a hash table object at the beginning of the file with the parameters that we’re going to set. This is so that we can use this hash table object in the other integration file (*.Integration.Tests.ps1) when we’re comparing the values that are expected to be set.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
As you can see in the example above, I create a $VirtualDisk hash table that contains all the parameters and values that will be used to test this DSC Resource. The $VirtualDisk object is then also accessible in the *.Integration.Tests.ps1 file.
Modifying the Integration Tests File
Now that the integration tests config file has been completed it is time to move on to the integration test script (*.Integration.Tests.ps1) itself, so open it in your editor of choice:
Next, customize the TODO area in the header with the your DSC Resource Name and DSC Module Name:
Feel free to remove the TODO comments if you want (I always do).
Initialization Code
After customizing the header we need to add any code that might be required to set this machine up to actually perform these integration tests. The first thing I like to do is add code to check that these integration tests can actually be performed on this machine. In my example resource, the iSCSI Virtual Disk resource will require the iSCSI Target Server feature to be installed, which also means the OS must be a Server OS. So, first thing in the try/catch block I add these checks:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This will cause the try/catch block to be exited straight away if these tests can’t actually be performed on this machine.
Note: The cleanup code in the finally block will still be called if we exit with a break command.
After this you might also need to add code to configure anything that these integration tests might depend on. For example, if you were implementing integration tests for testing an iSCSI Server Target, you’d need to make sure that there was an iSCSI Virtual Disk available to use, so you’d need to create one at this point. However, in the integration tests for the iSCSI Virtual Disk resource I don’t need anything else.
Testing the Resource Was Applied
Next, we need to add the tests that check that after the DSC Configuration has been applied to the machine that the changes have actually been made and that the parameters match those set by the Configuration:
To do this, we complete this section:
In this case, I’ve changed it to:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
What this code does is gets the iSCSI Virtual Disk that is at the path specified in the $VirtualDisk.path into a variable $VirtualDiskNew.
The parameters in $VirtualDiskNew are then matched to ensure they are the same as those in the $VirtualDisk hash table object that was created in the DSC Configuration script (*.config.ps1).
Cleaning Up
It is important that after the tests have been run that any changes that were made to the testing computer are reverted. So, after the end of the last test I add any clean up code. In my case, I want to remove the iSCSI Virtual Disk that was created:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The above code just removes the iSCSI Virtual Disk and then also makes sure that the VHD file was also deleted. This is also very important because if the clean up does not occur and the tests are run again on the same computer they may fail.
And We’re Done!
Now, that may all seem like quite a bit of work, but it becomes second nature after creating a few of them. They will also save you far more time in addressing future issues with the resource every time you make a simple change to the MOF (but forget to change the resource code). These tests will give users and other maintainers much more confidence in your resources as well.
This series actually ended up being a bit longer than I intended, but hopefully you’ve stuck with it and it has helped in some small way. If you’ve got this far and you’re wanting to know what to do next, why not head over to the PowerShell DSCResources GitHub repository and see if you could help out on some resources. You could start off adding some small but useful parameter to an existing resource, fixing a bug or contribute an entire new resource to an existing module. There are numerous issues that need to be addressed on these resources, many of which are requests for new features or resources.
If you have an idea for a new resource in an existing module, raise an issue in the DSC Resource Module repository and offer to create the new resource. You may find that someone is already working on one, but if not, then this is a great opportunity to get started. It is quite a rewarding feeling the first time one of your contributions gets published in the official community DSC Resources!
The purpose of this series of articles is to try and document a few of the lessons I learned while releasing new DSC resources as well as contributing to the existing Microsoft Community DSC resources. These articles are not intended to tell you how to write DSC resources from a programming perspective, but to give you some ideas on what might be expected of a DSC resource you’re releasing to the public. For example, unit and integration tests (don’t worry if you aren’t familiar with those terms).
These articles are also not intended to tell you what you must do to release your resource, but more document what will help your resource be easier to use and extend by other people. Some of these these things are obvious for people who have come from the development community, but may be quite new to operations people.
If you missed any previous articles you can find them here:
In the last couple of articles I covered the importance of automated testing and covered unit testing in particular (I’ll get to integration testing later). I had covered creating new unit tests using the unittest templates that are available here (although they will probably move here). I also covered how to complete the Pester Test Initialization and the Get-TargetResource and Set-TargetResource function areas of the unit test.
Unit Testing Completion
The final task in completing the unit tests is to complete the Set-TargetResource in tests and also optionally tests for any other supporting functions your DSC Resource may have required.
In these unit tests I am using a DSC Resource for creating iSCSI Virtual Disks to illustrate the process. You don’t need to know anything about iSCSI Virtual Disks to understand these articles or resources, but if you’re interested to know the cmdlets I’m using for these, see this page. I’m using the *_iSCSIVirtualDisk cmdlets in this DSC Resource.
Function Test-TargetResource
This area will contain the actual Pester tests that test the Test-TargetResource function. These are fairly similar to the Set-TargetResource except we will be checking these two things:
The output of the Test-TargetFunction is correct. E.g. it returns false if changes are required, which will cause Set-TargetFunction to be called.
The expected Mocks are called by the Function.
This area may contain a large number of tests depending on the complexity of your DSCResource. In most cases, you should expect there to create the tests from the following list, but often you will need even more for 100% code coverage:
Does the function return false when the resource being configureddoes exist and should, but one of the configured parameters does not match the current values? This test is usually repeated for each parameter in the DSC Resource.
Does the function return true when the resource being configureddoes exist and should, and all the configured parameters match the current values?
Does the function return false when the resource being configureddoes not exist but should?
Does the function return false when the resource being configured does exist but should not?
Does the function return true when the resource being configured does not exist and should not?
The bottom four of these tests are very similar. So I’ll only show examples of the top two contexts here.
Context ‘Virtual Disk exists and should but has a different …’
In this scenario we Mock the Get-iSCSIVirtualDisk cmdlet to return the object we defined in the Pester Test Initialization section. This is the behavior we’d expect if the resource being configured does exist:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Should return false – The Test-TargetResource should return false because we are changing the Description parameter so that the resource will require changes (e.g. Set-TargetResource should be called).
Should call the expected mocks – The Test-TargetResource should call the mocked cmdlets the expected number of times. In all contexts in this function this will always be just once.
The purpose of cloning the $TestVirtualDisk object is so we can modify the properties to simulate a property difference without modifying the $TestVirtualDisk object.
You should expect to repeat this context for each parameter that might be different.
Context ‘Virtual Disk does not exist but should’
In this scenario we Mock the Get-iSCSIVirtualDisk cmdlet to return nothing. This is the behavior we’d expect if the resource being configured does not exist:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
As you can see, there is not too much different with these tests and you shouldn’t have any problems figuring out the remaining ones. Just remember, the goal is always to get 100% code coverage.
Unit Testing Supporting Functions
It is quite common that you might have implemented some supporting functions in your DSC Resource. These supporting functions are usually called by your standard *-TargetResource functions. If that is the case there are two important things you should do:
Write unit tests that cover all code paths in your supporting functions.
Add mocks to your *-TargetResourceunit tests that prevent any constructive/destructive cmdlets that exist in your supporting functions from being called.
The first item is fairly self explanatory. For example, I often implement a get-* function in my DSC Resources which is used to pull the actual objects that will be used by the *-TargetResource functions (e.g. Get-VirtualDisk):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
To unit test this function I’d write unit tests that tested the following contexts:
Context ‘Virtual Disk does not exist’
Context ‘Virtual Disk does exist’
For example:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Earn a Chocolate Fish: If you look at the above supporting function and unit tests carefully, you’ll notice that I haven’t got 100% code coverage on it!
To get 100% code coverage I would have had to implement a unit test that covered the situation where the Get-iSCSIVirtualDisk function threw an exception that wasn’t a [Microsoft.Iscsi.Target.Commands.IscsiCmdException] exception.
In case you’re wondering, the Get-iSCSIVirtualDisk function throws a [Microsoft.Iscsi.Target.Commands.IscsiCmdException] when the cmdlet is called with the path parameter set to a path that does not contain a valid iSCSIVirtual Hard Disk file.
Unit Testing Exceptions
When creating unit tests you’ll often need to test a scenario where the function that is being tested is expected to throw an exception. If you read the Pester documentation, you’d might write a test for an exception like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This would of course will work. It will ensure that the code throws an exception in this situation. The problem is we aren’t really sure if it is the exception that we expected it to throw. It could have been thrown by some other part of our code.
So to improve on this we need to do things:
Customize the exception that is thrown.
Change the unit test so that it checks for the customized exception.
Customize the Exception
To create a custom exception we need to create a new exception object containing our custom error message. The exception object is then used to create a custom Error Record:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
In the above code, you just need to customize the $errorId and $errorMessage variables. The $errorId should just contain a simply string identifier for this particular type of error, but the $errorMessage can contain a full description of the error, including related parameters.
Once you’ve created the $errorRecord object you can call the ThrowTerminatingError method of the $PSCmdLet object, passing the $errorRecord object as the parameter.
Important: the $PSCmdLet object is only available in Functions that include the [CmdletBinding()] function attribute. So ensure your *-TargetResource and supporting functions include this attribute if you want to be able to access this object.
Test for the Customized Exception
To test for the custom exception object we need to create an identical object in the unit test and test for it:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The above code creates an identical exception object to the one produced by the exception in our DSC Resource code. The exception object can then be passed to the shouldthrow cmdlet. If a different exception is thrown by the code then the test will fail – it will only pass if the exception object is exactly the same.
Important: Make sure both the $errorId and $errorMessage variables are exactly the same as what would be produced by the code when your unit test calls it. This includes ensuring that if your $errorMessage contains any parameters that the unit test $errorMessage contains the same parameter values.
That about completes creating unit tests. After you’ve implemented a few unit tests you’ll no doubt come up with your own method of implementing them, but hopefully this has given you a place to start.
Up Next – Integration Tests
In the next article, I’ll cover the integration tests. There are often the most difficult to implement, but if you can take the time to implement them then your DSC Resources are guaranteed to be extremely robust and bugs are far less likely to slip through.