Microsoft Desired State Configuration (DSC) v3 represents the next generation of configuration management with cross-platform support. By creating class-based resources, you can build reusable components that work across both modern DSC v3 and legacy PSDSC engines. This tutorial walks you through creating your first DSC resource from scratch.
In this tutorial, you learn how to:
A DSC (Desired State Configuration) resource is a building block that tells the engine how to reach and maintain a desired state. Think of it as a PowerShell class with four lifecycle methods:
Method | Purpose | Returns | Compatibility |
---|---|---|---|
Get() |
Retrieves current state | Hashtable with current values | All |
Test() |
Validates desired state | $true if compliant, $false if not |
All |
Set() |
Applies desired state | Nothing (void) | All |
Export() |
Exports configuration | Array of resource instances | V3 only |
Class-based DSC resources offer a modern, object-oriented approach to configuration management in PowerShell. Benefits include:
While class-based resources are currently the recommended pattern for PowerShell DSC resource authoring, DSC v3 supports multiple languages and resource types. You can author resources in Go, C#, Python and other languages. Class-based resources remain well-supported but may not be the only model in the future.
For example, if your organization migrates from Windows-only infrastructure to a mix of Windows and Linux, or adopts newer versions of PowerShell, class-based resources help ensure your automation investments remain valid and portable across both the new Microsoft DSC v3 engine and the older PSDSC engine.
In the next section, you will learn to set up a project structure with a class-based DSC resource named DemoDscClass
.
Create a new folder for your DSC resource module:
# Create and navigate to project folder
mkdir DemoDscClass
cd DemoDscClass
Your project structure should look like this:
DemoDscClass/
├── DemoDscClass.psd1 # Module manifest
└── DemoDscClass.psm1 # Resource implementation
Your module manifest filename must match the project folder name. For example, if your folder is named DemoDscClass
, your manifest must be DemoDscClass.psd1
.
$manifestParams = @{
Path = '.\DemoDscClass.psd1'
RootModule = 'DemoDscClass.psm1'
DscResourcesToExport = 'DemoDscClass'
ModuleVersion = '0.0.1'
CompatiblePSEditions = @('Desktop', 'Core')
PowerShellVersion = '5.1'
}
New-ModuleManifest @manifestParams
You can also create DemoDscClass.psd1
using any text editor:
@{
RootModule = 'DemoDscClass.psm1'
ModuleVersion = '0.0.1'
CompatiblePSEditions = 'Desktop', 'Core'
GUID = '03ac4e95-e504-468d-add4-f099e2368239' # Change the GUID using New-Guid command
Author = 'username'
CompanyName = 'Unknown'
Copyright = '(c) developer. All rights reserved.'
PowerShellVersion = '5.1'
FunctionsToExport = '*'
CmdletsToExport = '*'
VariablesToExport = '*'
AliasesToExport = '*'
DscResourcesToExport = 'DemoDscClass'
PrivateData = @{
PSData = @{
}
}
}
More properties are available to set. For more information, read the article about_Module_Manifest
Create DemoDscClass.psm1
and add the basic class definition:
[DscResource()] # Marks this class as a DSC resource for discovery
class DemoDscClass {
[DscProperty(Key)] # Key property (uniquely identifies resource)
[System.String] $Key
[DscProperty()] # Optional property
[System.String] $OptionalProperty
DemoDscClass() { # Constructor (optional)
# initialization logic here
}
}
The Get()
method retrieves the current state:
[DemoDscClass] Get() {
Write-Verbose -Message 'Get called'
$currentState = @{
Key = $this.Key
}
return $currentState
}
The Test()
method checks if the current state matches the desired state:
[System.Boolean] Test() {
Write-Verbose -Message 'Test called - always returns $false to demo Set()'
return $false # Always returns false for demo
}
The Set()
method applies the desired state:
[void] Set() {
Write-Verbose -Message 'Set called - no changes are applied for demo'
}
The Export()
method exports every instance of a set of resources in Microsoft DSC v3.
static [DemoDscClass[]] Export() {
Write-Verbose -Message 'Export called - returning three demo instances'
$resultList = [System.Collections.Generic.List[DemoDscClass]]::new()
1..3 | ForEach-Object -Process {
$obj = New-Object DemoDscClass
$obj.Key = 'Demo{0}' -f $_
$obj.OptionalProperty = 'Value of OptionalProperty for Demo{0}' -f $_
$resultList.Add($obj)
}
return $resultList.ToArray()
}
You can only call the Export()
method by instantiating the class. Prior DSC versions can’t directly invoke the capability. Also remember, not all classes should implement Export()
. Always ask two questions:
Export()
?For example, exporting all Windows services from a machine makes sense and is relatily cheap. Exporting the whole registry might make sense, but isn’t a cheap operation to perform.
Here’s the complete DemoDscClass.psm1
file:
[DscResource()] # Attribute marking class as DSC resource
class DemoDscClass {
[DscProperty(Key)] # Declares a key property (must uniquely identify the resource instance), at least one key property is required for all DSC resources
[System.String] $Key
[DscProperty()]
[System.String] $OptionalProperty # Optional DSC property
DemoDscClass() {
# init logic here
}
[DemoDscClass] Get() { # Returns the current state as a hashtable (DSC accepts either a class instance or a hashtable)
Write-Verbose -Message 'Get called'
$currentState = @{
Key = $this.Key
}
return $currentState
}
[System.Boolean] Test() {
# Checks if the current state matches the desired state Write-Verbose -Message 'Test called - always returns $false to demo Set()'
return $false # Always returns false to force Set() for demo
}
[void] Set() { # Applies the desired state (no-op in this demo)
Write-Verbose -Message 'Set called - no changes are applied for demo'
}
static [DemoDscClass[]] Export() { # New capability in Microsoft DSC v3
Write-Verbose -Message 'Export called - returning three demo instances'
$resultList = [System.Collections.Generic.List[DemoDscClass]]::new()
1..3 | ForEach-Object -Process { # In a real resource, return an array of actual resource instances
$obj = New-Object DemoDscClass
$obj.Key = 'Demo{0}' -f $_
$obj.OptionalProperty = 'Value of OptionalProperty for Demo{0}' -f $_
$resultList.Add($obj)
}
return $resultList.ToArray()
}
}
It’s time to install Microsoft DSC v3 and test out the class using dsc
.
To easily install the dsc
executable, you can use the PSDSC PowerShell community module.
The community module only works on PowerShell 7.
Install-PSResource PSDSC -TrustRepository -Quiet
Install-DscExe -IncludePrerelease -Force
dsc --version
The command also works on other platforms. You can also grab the asset on GitHub for the platform you’re working on.
Add your project folder to the module path:
$env:PSModulePath += [System.IO.Path]::PathSeparator + (Split-Path -Path (Get-Location) -Parent)
This commands adds your current directory to be searchable. PowerShell can now find your DSC resource when you list or invoke capabilities against it.
dsc resource list --adapter Microsoft.DSC/PowerShell
If you want to find the DemoDscClass
, you can filter on it:
dsc resource list --adapter Microsoft.DSC/PowerShell DemoDscClass*
You should see DemoDscClass
in the list.
$desiredParameters = @{ Key = 'Demo' } | ConvertTo-Json -Compress
# Get current state
dsc resource get --resource DemoDscClass/DemoDscClass --input $desiredParameters
# Test compliance
dsc resource test --resource DemoDscClass/DemoDscClass --input $desiredParameters
# Apply configuration
dsc resource set --resource DemoDscClass/DemoDscClass --input $desiredParameters
# Export instances
dsc resource export --resource DemoDscClass/DemoDscClass
To see detailed trace logs, add the --trace-level trace
parameter to any DSC command. DSC’s default output is YAML if nothing is piped. If you want the raw JSON, you can add the --output-format json
option.
You can also define a configuration document, allowing you to define multiple resource instances. The next section illustrates how you can create such a configuration document.
Create demo.dsc.config.yaml
:
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Demo Dsc Class
type: Microsoft.DSC/PowerShell
properties:
resources:
- name: Demo Dsc Class 1
type: DemoDscClass/DemoDscClass
properties:
Key: Demo
# Get current state
dsc config get --file demo.dsc.config.yaml
# Test compliance
dsc config test --file demo.dsc.config.yaml
# Apply configuration
dsc config set --file demo.dsc.config.yaml
# Export configuration
dsc config export --file demo.dsc.config.yaml
Microsoft DSC v3 allows you to implicitly call the DSC class-based resource. Create demo-implicit.dsc.config.yaml
for a simpler approach:
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Demo Dsc Class 1
type: DemoDscClass/DemoDscClass
properties:
Key: Demo
You can now run the configuration file with the same commands ran earlier.
Using implicit syntax comes at a performance cost. Each adapter runs through the DSC engine to find the relative DSC resource. If you run this command for the first time, it can take a while as no caching has taken place.
Install-PSResource -Name Pester
Create DemoDscClass.tests.ps1
:
BeforeAll {
$modulePath = $PSScriptRoot
Import-Module "$modulePath/DemoDscClass.psd1" -Force
}
Describe 'DemoDscClass' {
Context 'Get() method' {
It 'Returns the same Key in Get()' {
$desiredParameters = @{ Key = 'X' } | ConvertTo-Json -Compress
$out = dsc resource get --resource DemoDscClass/DemoDscClass --input $desiredParameters | ConvertFrom-Json
$out.actualState.Key | Should -Be 'X'
}
}
Context 'Export() method' {
It 'Returns multiple instances in Export()' {
$outArray = dsc resource export --resource DemoDscClass/DemoDscClass | ConvertFrom-Json
$outArray.resources | Should -HaveCount 3
$outArray.resources[0].properties.Key | Should -BeLike 'Demo*'
}
}
}
Invoke-Pester -Script ./DemoDscClass.tests.ps1 -Output Detailed
PowerShell cannot reload class definitions in the current session. After modifying a class, you must restart PowerShell for changes to take effect.
If you receive an error when running dsc
, verify the installation:
# Check if dsc is in PATH
Get-Command dsc -ErrorAction SilentlyContinue
# If not found, add to PATH for current session (Windows only)
$env:PATH += [System.IO.Path]::PathSeparator + (Join-Path -Path $env:LOCALAPPDATA -ChildPath 'dsc')
If you encounter the following error:
ERROR Operation: Failed to parse JSON from 'get': executable = 'pwsh' stdout = 'VERBOSE: Get called
{"result":[{"name":"DemoDscClass/DemoDscClass","type":"DemoDscClass/DemoDscClass","properties":{"Key":"Demo"}}]}
' stderr = '' -> expected value at line 1 column 1
You should remove any output that clutters the STDIN:
# Remove these from your methods:
Write-Verbose "message" # Causes JSON parsing issues
Write-Warning "message" # Causes JSON parsing issues
Write-Debug "message" # Causes JSON parsing issues
The issue is currently open on GitHub.
To share your resource on PowerShell Gallery:
Publish-PSResource -Path .\ -Repository PSGallery -ApiKey <YOUR-API-KEY>
Congratulations – you have built, tested, and executed your very first class-based DSC v3 resource!
By following this guide, you have learned how to:
Now that you’ve created your first DSC class-based resource, explore these advanced topics:
DemoDscClass
with additional properties and real-world logic, such as file or registry management.Test()
to detect actual configuration drift.