Summary: Microsoft MVP, Will Anderson, teaches how to create a DSC resource by using a Windows PowerShell script.
Hello. Ed Wilson here, bringing you MVP, Will Anderson, and Part 6 of his posts about using Desired State Configuration (DSC). If you have not already done so, please read the previous posts in the series:
- Desired State Configuration: Part 1
- Desired State Configuration: Part 2
- Desired State Configuration: Part 3
- Desired State Configuration: Part 4
- Desired State Configuration: Part 5
Note The code used in this series can be downloaded from the Script Center Repository:
Conceptualize Desired State Configuration - Reference Script.
As we saw in our last post, you can use your scripts inside of the Script DSC resource provider to configure your system. But to do so, you need to do a lot of additional work—and in comparison to using a DSC resource, it can look pretty sloppy. However, if you've pulled apart a DSC resource, it can look quite intimidating to build.
Here's where the DSC Resource Designer comes in. You can find the latest version in the PowerShell Gallery as xDscResourceDesigner. Let's use Windows PowerShell to get it:
PS C:\Windows\system32> Find-Module xDscResourceDesigner | Install-Module
If we look at the commands in the module, you'll find the following:
PS C:\Windows\system32> Get-Command -Module xDSCResourceDesigner
CommandType Name Version Source
----------- ---- ------- ------
Function Import-xDscSchema 1.6.0.0 xDSCResourceDesigner
Function New-xDscResource 1.6.0.0 xDSCResourceDesigner
Function New-xDscResourceProperty 1.6.0.0 xDSCResourceDesigner
Function Test-xDscResource 1.6.0.0 xDSCResourceDesigner
Function Test-xDscSchema 1.6.0.0 xDSCResourceDesigner
Function Update-xDscResource 1.6.0.0 xDSCResourceDesigner
Note There's a lot of great information about using the xDscResourceDesigner on the project's GitHub page: PowerShell/xDSCResourceDesigner. I highly encourage you to take a look.
In the meantime, we're going to look at how we can use this to create a custom DSC resource. I'm not going to use the NO_SMS_ON_DRIVE.sms file script that we added to our configuration in the last post. I'm going to keep that there as our “resource of shame” so we can remember why this is so much better.
My systems are spinning up with a single disk, partitioned with only a drive C and 127 GB of disk allocated to it. But that's way too much storage for my server core instance, so I want to shrink the existing disk, and create a new partition. For this, we're going to create not one, but two, DSC resources! So let's begin.
Figuring out parameters
Before we can create the resource, we need to know what we're going to put in to it. So let's think about our first step, which is to resize our C partition. In PowerShell terms, that equates to:
Resize-Partition -DriveLetter $DriveLetter -Size $DiskSize
That's easy enough. We need two parameters to change the size of an existing partition. Let's create the first resource:
New-xDscResource -Name DiskResize -Property $DiskSize,$DriveLetter,$Ensure -Path 'C:\Program Files\WindowsPowerShell\Modules\' -ModuleName DiskSize
Here, we're creating a new DSC resource called DiskResize (Name) and assigning the parameters, or properties (DiskSize,DriveLetter,Ensure) to the resource. We're building it straight in our module’s directory, and giving the module the name of DiskSize. Before we can execute this code, we need to declare our property variables:
$DiskSize = New-xDscResourceProperty -Name DiskSize -Type Uint64 -Attribute Required
$DriveLetter = New-xDscResourceProperty -Name DriveLetter -Type String -Attribute Key
$Ensure = New-xDscResourceProperty –Name Ensure -Type String -Attribute Write –ValidateSet “Present”, “Absent”
New-xDscResource -Name DiskResize -Property $DiskSize,$DriveLetter,$Ensure -Path 'C:\Program Files\WindowsPowerShell\Modules\' -ModuleName DiskSize
We're creating our DSC resource properties. The Name is obviously the name of the parameter we're setting. The Type is the expected input type. This is important for validation of the input data. And then there's the Attribute, of which your available options are Key, Read, Write, and Required.
Note You must select one property as Key, and only one property can be made the Key. This property must be unique! So if you're planning to use the DSC resource more than once in your configuration, you want to make sure this is a property that won't be called again in a later configuration. If you need to leverage that Key value more than once, you can do so later with hash tables...we'll get into that at a later date.
Now that we've got our properties, let's execute!
PS C:\Windows\system32> $DiskSize = New-xDscResourceProperty -Name DiskSize -Type Uint64 -Attribute Required
$DriveLetter = New-xDscResourceProperty -Name DriveLetter -Type String -Attribute Key
$Ensure = New-xDscResourceProperty –Name Ensure -Type String -Attribute Write –ValidateSet “Present”, “Absent”
New-xDscResource -Name DiskResize -Property $DiskSize,$DriveLetter,$Ensure -Path 'C:\Program Files\WindowsPowerShell\Modules\' -ModuleName DiskSize
Directory: C:\Program Files\WindowsPowerShell\Modules
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 1/7/2016 6:12 PM DiskSize
Directory: C:\Program Files\WindowsPowerShell\Modules\DiskSize
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 1/7/2016 6:12 PM DSCResources
Directory: C:\Program Files\WindowsPowerShell\Modules\DiskSize\DSCResources
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 1/7/2016 6:12 PM DiskResize
As you can see, some new directories have been created. Also some new files have been created.
PS C:\Windows\system32> Get-ChildItem -Path 'C:\Program Files\WindowsPowerShell\Modules\DiskSize' -Recurse -Exclude Directory
Directory: C:\Program Files\WindowsPowerShell\Modules\DiskSize
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 1/7/2016 6:12 PM DSCResources
Directory: C:\Program Files\WindowsPowerShell\Modules\DiskSize\DSCResources
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 1/7/2016 6:12 PM DiskResize
Directory: C:\Program Files\WindowsPowerShell\Modules\DiskSize\DSCResources\DiskResize
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1/7/2016 6:12 PM 3946 DiskResize.psm1
-a---- 1/7/2016 6:12 PM 508 DiskResize.schema.mof
Directory: C:\Program Files\WindowsPowerShell\Modules\DiskSize
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1/7/2016 6:12 PM 6574 DiskSize.psd1
Let's take a look at the module manifest under C:\Program Files\WindowsPowerShell\Modules\DiskSize. This will be the file that provides the data when you look at the properties of a module. I've modified some of the basic information in the DiskSize manifest.
#
# Module manifest for module 'DiskSize'
#
# Generated by: Will Anderson
#
# Generated on: 1/5/2016
#
@{
# Script module or binary module file associated with this manifest.
# RootModule = ''
# Version number of this module.
ModuleVersion = '1.0.0.0'
# ID used to uniquely identify this module
GUID = '507d2597-0903-48a9-bd38-291bfbee8419'
# Author of this module
Author = 'Will Anderson'
# Company or vendor of this module
CompanyName = 'Last Word In Nerd'
# Copyright statement for this module
Copyright = '(c) 2016 lastwordinnerd.com. All rights reserved.'
# Description of the functionality provided by this module
# Description = 'Provides DSC Resources to resize existing disk partitions and create new ones.'
# Minimum version of the Windows PowerShell engine required by this module
# PowerShellVersion = '4.0'
# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module
# CLRVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module
FunctionsToExport = '*'
# Cmdlets to export from this module
CmdletsToExport = '*'
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module
AliasesToExport = '*'
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
When we pull the properties of the newly created module, here’s what we see:
PS C:\Windows\system32> Get-Module DiskSize | Select-Object *
LogPipelineExecutionDetails : False
Name : DiskSize
Path : C:\Program Files\WindowsPowerShell\Modules\DiskSize\DiskSize.psd1
ImplementingAssembly :
Definition :
Description :
Guid : 507d2597-0903-48a9-bd38-291bfbee8419
HelpInfoUri :
ModuleBase : C:\Program Files\WindowsPowerShell\Modules\DiskSize
PrivateData : {PSData}
Tags : {}
ProjectUri :
IconUri :
LicenseUri :
ReleaseNotes :
RepositorySourceLocation :
Version : 1.0.0.0
ModuleType : Manifest
Author : Will Anderson
AccessMode : ReadWrite
ClrVersion :
CompanyName : Last Word In Nerd
Copyright : (c) 2016 lastwordinnerd.com. All rights reserved.
You'll see the information is displayed. So now let's take a look at the DiskResize.psm1 file under C:\Program Files\WindowsPowerShell\Modules\DiskSize\DSCResources\DiskResize. This is where the meat and potatoes of our DSC resource are built.
function Get-TargetResource
{
[CmdletBinding()]
[OutputType([System.Collections.Hashtable])]
param
(
[parameter(Mandatory = $true)]
[System.UInt64]
$DiskSize,
[parameter(Mandatory = $true)]
[System.String]
$DriveLetter
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
<# $returnValue = @{ DiskSize = [System.UInt64] DriveLetter = [System.String] Ensure = [System.String] } $returnValue #>
}
function Set-TargetResource
{
[CmdletBinding()]
param
(
[parameter(Mandatory = $true)]
[System.UInt64]
$DiskSize,
[parameter(Mandatory = $true)]
[System.String]
$DriveLetter,
[ValidateSet("Present","Absent")]
[System.String]
$Ensure
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
#Include this line if the resource requires a system reboot.
#$global:DSCMachineStatus = 1
}
function Test-TargetResource
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[System.UInt64]
$DiskSize,
[parameter(Mandatory = $true)]
[System.String]
$DriveLetter,
[ValidateSet("Present","Absent")]
[System.String]
$Ensure
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
<# $result = [System.Boolean] $result #>
}
Export-ModuleMember -Function *-TargetResource
Understanding the DSC resource
As you can see, the xDscResourceDesigner has already built a template for us to use, and even inserted some nice comments into it so you know where to put what. Let's start with the Get-TargetResource function first.
Get-TargetResource, like the GetScript block of the Script resource, pulls a hash table of the input parameters. You'll notice that there's a comment block that's already generated a nice little hash table for us. So we can remove the comment tags and use the template provided:
function Get-TargetResource
{
[CmdletBinding()]
[OutputType([System.Collections.Hashtable])]
param
(
[parameter(Mandatory = $true)]
[System.UInt64]
$DiskSize,
[parameter(Mandatory = $true)]
[System.String]
$DriveLetter
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
$returnValue = @{
DiskSize = [System.UInt64]
DriveLetter = [System.String]
Ensure = [System.String]
}
$returnValue
}
I'm going to skip to the Test-TargetResource block. Like the TestScript block from the previous post, we need to return a True or False statement so DSC can decide if it should proceed to the Set-TargetResource step. This will be a little easier than our SMS file statement because we're not dealing with an array in this example. We'll use the Get-Partition cmdlet to test it.
PS C:\Windows\system32> $DriveLetter = 'C'
PS C:\Windows\system32> ((Get-Partition -DriveLetter $DriveLetter).Size / 1GB)
40
PS C:\Windows\system32> $DiskSize = 40GB
PS C:\Windows\system32> ((Get-Partition -DriveLetter $DriveLetter).Size -eq $DiskSize)
True
PS C:\Windows\system32> $DiskSize = 20GB
PS C:\Windows\system32> ((Get-Partition -DriveLetter $DriveLetter).Size -eq $DiskSize)
False
I’ve used Get-Partition against my system drive to get the size in GB and then set that as my $DiskSize variable. Then I executed a True/False query to verify that with the given parameters, I return a True statement. Then, I change the $DiskSize variable to 20 GB, which I know is not the size of my drive, and execute the code to get a False statement. This validates that my test command should be valid. Now I'll insert it into the Test-TargetResource block.
function Test-TargetResource
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[System.UInt64]
$DiskSize,
[parameter(Mandatory = $true)]
[System.String]
$DriveLetter,
[ValidateSet("Present","Absent")]
[System.String]
$Ensure
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
((Get-Partition -DriveLetter $DriveLetter).Size -eq $DiskSize)
}
Now that we have the Get-TargetResource and Test-TargetResource blocks finished, we can move on to Set-TargetResource, which is the easiest by far.
function Set-TargetResource
{
[CmdletBinding()]
param
(
[parameter(Mandatory = $true)]
[System.UInt64]
$DiskSize,
[parameter(Mandatory = $true)]
[System.String]
$DriveLetter,
[ValidateSet("Present","Absent")]
[System.String]
$Ensure
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
Resize-Partition -DriveLetter $DriveLetter -Size $DiskSize
#Include this line if the resource requires a system reboot.
#$global:DSCMachineStatus = 1
}
If you notice, the template gives you a commented line that you can use if you want to set the global DSCMachineStatus variable to flag the system for a reboot. This can be pretty handy if you're making a change that requires a reboot.
Let's save our work, and re-import the module:
PS C:\Windows\system32> Get-DscResource -Module DiskSize
ImplementedAs Name ModuleName Version Properties
------------- ---- ---------- ------- ----------
PowerShell DiskResize DiskSize 1.0.0.0 {DiskSize, DriveLetter, DependsOn, Ensure...}
~Will
Thanks, Will. Tune in tomorrow for Will’s last installment of this DSC blog series.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. Also check out my Microsoft Operations Management Suite Blog. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy