What is PowerShell Desired State Configuration?
PowerShell Desired State Configuration (DSC) is a declarative configuration management platform built into Windows PowerShell. Instead of writing procedural scripts that describe how to configure a system, DSC allows you to declare what state you want a system to be in, and PowerShell handles the implementation details.
DSC operates on a simple principle: you define the desired configuration state, and DSC ensures that target systems maintain that state. If a system drifts from the desired configuration, DSC can automatically detect and correct it.
Core DSC Concepts
Resources
DSC Resources are the building blocks of DSC configurations. Each resource manages a specific aspect of system configuration, such as files, services, registry keys, or software installations. PowerShell includes built-in resources like File, Service, Registry, and WindowsFeature.
Configurations
A Configuration is a PowerShell function that defines how resources should be configured. Configurations are written in a declarative syntax and compiled into Managed Object Format (MOF) files.
Local Configuration Manager (LCM)
The LCM is the DSC engine that runs on each target node. It processes MOF files, applies configurations, and monitors system state. The LCM can operate in different modes: Push, Pull, or Push-Pull hybrid.
Creating Your First DSC Configuration
Let’s create a basic DSC configuration that ensures a specific file exists with particular content:
# Define the configuration
Configuration EnsureWebConfig {
# Input parameters for the configuration
param (
[string]$NodeName = 'localhost',
[string]$FilePath = 'C:\inetpub\wwwroot\web.config'
)
# Import DSC resources
Import-DscResource -ModuleName PSDesiredStateConfiguration
# Node block specifies target systems
Node $NodeName {
# File resource ensures the file exists
File WebConfigFile {
DestinationPath = $FilePath
Contents = @"
"@
Ensure = 'Present'
Type = 'File'
}
# Ensure the parent directory exists
File WebRoot {
DestinationPath = 'C:\inetpub\wwwroot'
Ensure = 'Present'
Type = 'Directory'
}
}
}
# Compile the configuration to generate MOF file
EnsureWebConfig -OutputPath "C:\DSC\Configurations"
Output:
Directory: C:\DSC\Configurations
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 10/22/2025 3:45 PM 2156 localhost.mof
The configuration compiles into a MOF file that contains the declarative instructions for the target system.
Applying DSC Configurations
Once you’ve compiled a configuration, apply it using the Start-DscConfiguration cmdlet:
# Apply the configuration
Start-DscConfiguration -Path "C:\DSC\Configurations" -Wait -Verbose -Force
Output:
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply
VERBOSE: An LCM method call arrived from computer SERVER01 with user sid S-1-5-21-xxx
VERBOSE: [SERVER01]: LCM: [ Start Set ]
VERBOSE: [SERVER01]: LCM: [ Start Resource ] [[File]WebRoot]
VERBOSE: [SERVER01]: LCM: [ Start Test ] [[File]WebRoot]
VERBOSE: [SERVER01]: [[File]WebRoot] The system cannot find the path specified.
VERBOSE: [SERVER01]: LCM: [ End Test ] [[File]WebRoot] in 0.0320 seconds.
VERBOSE: [SERVER01]: LCM: [ Start Set ] [[File]WebRoot]
VERBOSE: [SERVER01]: LCM: [ End Set ] [[File]WebRoot] in 0.0940 seconds.
VERBOSE: [SERVER01]: LCM: [ End Resource ] [[File]WebRoot]
VERBOSE: [SERVER01]: LCM: [ Start Resource ] [[File]WebConfigFile]
VERBOSE: [SERVER01]: LCM: [ Start Test ] [[File]WebConfigFile]
VERBOSE: [SERVER01]: LCM: [ End Test ] [[File]WebConfigFile] in 0.0150 seconds.
VERBOSE: [SERVER01]: LCM: [ Start Set ] [[File]WebConfigFile]
VERBOSE: [SERVER01]: LCM: [ End Set ] [[File]WebConfigFile] in 0.0780 seconds.
VERBOSE: [SERVER01]: LCM: [ End Resource ] [[File]WebConfigFile]
VERBOSE: [SERVER01]: LCM: [ End Set ]
VERBOSE: Operation 'Invoke CimMethod' complete.
Working with DSC Resources
Built-in Resources
PowerShell includes numerous built-in DSC resources. Here’s an example using multiple resources to configure a web server:
Configuration WebServerConfig {
param (
[string]$NodeName = 'localhost'
)
Import-DscResource -ModuleName PSDesiredStateConfiguration
Node $NodeName {
# Ensure IIS Windows Feature is installed
WindowsFeature IIS {
Name = 'Web-Server'
Ensure = 'Present'
}
# Ensure IIS Management Console is installed
WindowsFeature IISManagementConsole {
Name = 'Web-Mgmt-Console'
Ensure = 'Present'
DependsOn = '[WindowsFeature]IIS'
}
# Ensure W3SVC service is running
Service W3SVC {
Name = 'W3SVC'
State = 'Running'
StartupType = 'Automatic'
DependsOn = '[WindowsFeature]IIS'
}
# Configure default website directory
File WebsiteContent {
DestinationPath = 'C:\inetpub\wwwroot\index.html'
Contents = 'DSC Configured Web Server
'
Ensure = 'Present'
Type = 'File'
DependsOn = '[WindowsFeature]IIS'
}
# Set registry key for logging
Registry IISLogging {
Key = 'HKLM:\SOFTWARE\Microsoft\InetStp'
ValueName = 'LoggingEnabled'
ValueData = '1'
ValueType = 'Dword'
Ensure = 'Present'
}
}
}
# Compile the configuration
WebServerConfig -OutputPath "C:\DSC\WebServer"
Notice the DependsOn property that establishes resource dependencies, ensuring resources are applied in the correct order.
Custom DSC Resources
You can create custom DSC resources for specialized configuration needs. Here’s a script-based custom resource:
# Create a custom resource module structure
$modulePath = "$env:ProgramFiles\WindowsPowerShell\Modules\CustomAppConfig"
$resourcePath = "$modulePath\DSCResources\CustomAppConfig"
New-Item -Path $resourcePath -ItemType Directory -Force
# Create the resource schema file
$schemaPath = "$resourcePath\CustomAppConfig.schema.mof"
@"
[ClassVersion("1.0.0"), FriendlyName("CustomAppConfig")]
class CustomAppConfig : OMI_BaseResource
{
[Key, Description("Application name")] String AppName;
[Write, Description("Configuration value")] String ConfigValue;
[Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
};
"@ | Out-File -FilePath $schemaPath -Encoding ascii
# Create the resource module
$modulePath = "$resourcePath\CustomAppConfig.psm1"
@"
function Get-TargetResource {
param (
[Parameter(Mandatory)]
[string]$AppName
)
$configFile = "C:\AppConfigs\$AppName.config"
$exists = Test-Path $configFile
if ($exists) {
$content = Get-Content $configFile -Raw
return @{
AppName = $AppName
ConfigValue = $content
Ensure = 'Present'
}
}
else {
return @{
AppName = $AppName
ConfigValue = $null
Ensure = 'Absent'
}
}
}
function Test-TargetResource {
param (
[Parameter(Mandatory)]
[string]$AppName,
[string]$ConfigValue,
[ValidateSet('Present','Absent')]
[string]$Ensure = 'Present'
)
$currentState = Get-TargetResource -AppName $AppName
if ($Ensure -eq 'Present') {
return ($currentState.Ensure -eq 'Present' -and $currentState.ConfigValue -eq $ConfigValue)
}
else {
return ($currentState.Ensure -eq 'Absent')
}
}
function Set-TargetResource {
param (
[Parameter(Mandatory)]
[string]$AppName,
[string]$ConfigValue,
[ValidateSet('Present','Absent')]
[string]$Ensure = 'Present'
)
$configFile = "C:\AppConfigs\$AppName.config"
if ($Ensure -eq 'Present') {
New-Item -Path 'C:\AppConfigs' -ItemType Directory -Force
Set-Content -Path $configFile -Value $ConfigValue
Write-Verbose "Configuration file created for $AppName"
}
else {
if (Test-Path $configFile) {
Remove-Item $configFile -Force
Write-Verbose "Configuration file removed for $AppName"
}
}
}
Export-ModuleMember -Function *-TargetResource
"@ | Out-File -FilePath $modulePath -Encoding utf8
Configuring the Local Configuration Manager
The LCM controls how DSC operates on a node. Configure it using a meta-configuration:
[DSCLocalConfigurationManager()]
Configuration LCMConfig {
Node localhost {
Settings {
# Set refresh mode to Push (default) or Pull
RefreshMode = 'Push'
# How often to check configuration (in minutes)
RefreshFrequencyMins = 30
# How often to reapply current configuration
ConfigurationModeFrequencyMins = 15
# What to do if configuration drift is detected
# ApplyOnly, ApplyAndMonitor, or ApplyAndAutoCorrect
ConfigurationMode = 'ApplyAndAutoCorrect'
# Allow module overwrite during configuration
AllowModuleOverwrite = $true
# Reboot if needed
RebootNodeIfNeeded = $true
# Action after reboot
ActionAfterReboot = 'ContinueConfiguration'
}
}
}
# Generate the meta-configuration MOF
LCMConfig -OutputPath "C:\DSC\LCM"
# Apply the LCM configuration
Set-DscLocalConfigurationManager -Path "C:\DSC\LCM" -Verbose
Output:
VERBOSE: Performing the operation "Start-DscConfiguration: SendMetaConfigurationApply" on target "MSFT_DSCLocalConfigurationManager".
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendMetaConfigurationApply
VERBOSE: An LCM method call arrived from computer SERVER01 with user sid S-1-5-21-xxx
VERBOSE: [SERVER01]: LCM: [ Start Set ]
VERBOSE: [SERVER01]: LCM: [ End Set ]
VERBOSE: Operation 'Invoke CimMethod' complete.
DSC Configuration Modes
Push Mode
In Push mode, administrators manually push configurations to target nodes using Start-DscConfiguration. This is ideal for small environments or one-time configurations.
# Push configuration to remote computer
Start-DscConfiguration -Path "C:\DSC\Configurations" `
-ComputerName "Server01", "Server02" `
-Credential (Get-Credential) `
-Wait -Verbose
Pull Mode
Pull mode is designed for enterprise environments. Nodes periodically check a pull server for configuration updates and apply them automatically.
# Configure LCM for Pull mode
[DSCLocalConfigurationManager()]
Configuration PullClientConfig {
Node localhost {
Settings {
RefreshMode = 'Pull'
RefreshFrequencyMins = 30
ConfigurationMode = 'ApplyAndAutoCorrect'
}
ConfigurationRepositoryWeb PullServer {
ServerURL = 'https://dscpull.contoso.com:8080/PSDSCPullServer.svc'
RegistrationKey = 'your-registration-key-here'
ConfigurationNames = @('WebServerConfig')
}
ReportServerWeb PullServerReports {
ServerURL = 'https://dscpull.contoso.com:8080/PSDSCPullServer.svc'
RegistrationKey = 'your-registration-key-here'
}
}
}
PullClientConfig -OutputPath "C:\DSC\PullClient"
Set-DscLocalConfigurationManager -Path "C:\DSC\PullClient" -Verbose
Advanced DSC Patterns
Parameterized Configurations
Create flexible configurations using parameters and configuration data:
Configuration EnvironmentConfig {
param (
[Parameter(Mandatory)]
[string]$Environment,
[Parameter(Mandatory)]
[hashtable]$ConfigurationData
)
Import-DscResource -ModuleName PSDesiredStateConfiguration
Node $AllNodes.NodeName {
$nodeConfig = $ConfigurationData.AllNodes | Where-Object { $_.NodeName -eq $Node.NodeName }
File AppConfig {
DestinationPath = "C:\Apps\config.json"
Contents = @"
{
"environment": "$Environment",
"databaseServer": "$($nodeConfig.DatabaseServer)",
"apiEndpoint": "$($nodeConfig.ApiEndpoint)",
"logLevel": "$($nodeConfig.LogLevel)"
}
"@
Ensure = 'Present'
Type = 'File'
}
Registry EnvironmentKey {
Key = 'HKLM:\SOFTWARE\MyApp'
ValueName = 'Environment'
ValueData = $Environment
ValueType = 'String'
Ensure = 'Present'
}
}
}
# Define configuration data
$configData = @{
AllNodes = @(
@{
NodeName = 'WebServer01'
DatabaseServer = 'sqlprod.contoso.com'
ApiEndpoint = 'https://api.contoso.com'
LogLevel = 'Warning'
},
@{
NodeName = 'WebServer02'
DatabaseServer = 'sqlprod.contoso.com'
ApiEndpoint = 'https://api.contoso.com'
LogLevel = 'Warning'
}
)
}
# Compile for production environment
EnvironmentConfig -Environment 'Production' -ConfigurationData $configData `
-OutputPath "C:\DSC\Production"
Composite Resources
Combine multiple resources into reusable composite resources:
Configuration WebApplication {
param (
[Parameter(Mandatory)]
[string]$WebsiteName,
[Parameter(Mandatory)]
[string]$ApplicationPath,
[Parameter(Mandatory)]
[string]$ApplicationPool
)
Import-DscResource -ModuleName PSDesiredStateConfiguration
# Ensure application directory exists
File ApplicationDirectory {
DestinationPath = $ApplicationPath
Ensure = 'Present'
Type = 'Directory'
}
# Ensure web.config exists
File WebConfig {
DestinationPath = "$ApplicationPath\web.config"
Contents = ' '
Ensure = 'Present'
Type = 'File'
DependsOn = '[File]ApplicationDirectory'
}
# Configure folder permissions
Script FolderPermissions {
GetScript = {
return @{ Result = (Get-Acl $using:ApplicationPath).AccessToString }
}
TestScript = {
$acl = Get-Acl $using:ApplicationPath
$permission = "IIS_IUSRS", "ReadAndExecute", "ContainerInherit, ObjectInherit", "None", "Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
return $acl.Access | Where-Object { $_.IdentityReference -eq $accessRule.IdentityReference }
}
SetScript = {
$acl = Get-Acl $using:ApplicationPath
$permission = "IIS_IUSRS", "ReadAndExecute", "ContainerInherit, ObjectInherit", "None", "Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$acl.AddAccessRule($accessRule)
Set-Acl $using:ApplicationPath $acl
}
DependsOn = '[File]ApplicationDirectory'
}
}
# Use the composite resource in a configuration
Configuration DeployWebApps {
Import-DscResource -ModuleName PSDesiredStateConfiguration
Node localhost {
WindowsFeature IIS {
Name = 'Web-Server'
Ensure = 'Present'
}
WebApplication IntranetApp {
WebsiteName = 'Intranet'
ApplicationPath = 'C:\inetpub\wwwroot\intranet'
ApplicationPool = 'IntranetAppPool'
DependsOn = '[WindowsFeature]IIS'
}
WebApplication ApiApp {
WebsiteName = 'API'
ApplicationPath = 'C:\inetpub\wwwroot\api'
ApplicationPool = 'ApiAppPool'
DependsOn = '[WindowsFeature]IIS'
}
}
}
Testing and Validating Configurations
Test Current Configuration State
# Test if the current configuration matches desired state
Test-DscConfiguration -Verbose
Output:
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters
VERBOSE: Test flag is True
VERBOSE: [SERVER01]: LCM: [ Start Test ]
VERBOSE: [SERVER01]: LCM: [ Start Resource ] [[File]WebConfigFile]
VERBOSE: [SERVER01]: LCM: [ End Resource ] [[File]WebConfigFile]
VERBOSE: [SERVER01]: LCM: [ End Test ]
VERBOSE: Operation 'Invoke CimMethod' complete.
True
Get Current Configuration Details
# Retrieve detailed configuration status
Get-DscConfiguration | Format-List
Output:
ConfigurationName : EnsureWebConfig
DependsOn :
ModuleName : PSDesiredStateConfiguration
ModuleVersion : 1.1
PsDscRunAsCredential :
ResourceId : [File]WebConfigFile
SourceInfo :
Contents : ...
CreatedDate :
DestinationPath : C:\inetpub\wwwroot\web.config
Ensure : Present
ModifiedDate : 10/22/2025 3:45:00 PM
Type : File
PSComputerName : localhost
Get LCM Configuration
# View LCM settings
Get-DscLocalConfigurationManager | Format-List
Output:
ActionAfterReboot : ContinueConfiguration
AgentId : B5C6D8E9-F123-4567-89AB-CDEF01234567
AllowModuleOverWrite : True
CertificateID :
ConfigurationDownloadManagers : {}
ConfigurationID :
ConfigurationMode : ApplyAndAutoCorrect
ConfigurationModeFrequencyMins : 15
Credential :
DebugMode : {NONE}
DownloadManagerCustomData :
DownloadManagerName :
LCMCompatibleVersions : {1.0, 2.0}
LCMState : Idle
LCMStateDetail :
LCMVersion : 2.0
MaximumDownloadSizeMB : 500
PartialConfigurations :
RebootNodeIfNeeded : True
RefreshFrequencyMins : 30
RefreshMode : Push
ReportManagers : {}
ResourceModuleManagers : {}
SignatureValidationPolicy : NONE
SignatureValidations : {}
StatusRetentionTimeInDays : 10
Troubleshooting DSC
View DSC Event Logs
# Get recent DSC operational events
Get-WinEvent -LogName "Microsoft-Windows-Dsc/Operational" -MaxEvents 20 |
Select-Object TimeCreated, Id, Message |
Format-Table -AutoSize -Wrap
Debug DSC Resources
# Enable debug mode for DSC
Set-DscLocalConfigurationManager -Path "C:\DSC\LCM" -Verbose -Force
# View detailed resource execution
$config = Get-DscConfiguration -ErrorAction SilentlyContinue
if (-not $config) {
Write-Warning "No configuration currently applied"
}
# Check for errors
$errors = Get-WinEvent -LogName "Microsoft-Windows-Dsc/Operational" -MaxEvents 50 |
Where-Object { $_.LevelDisplayName -eq "Error" }
if ($errors) {
$errors | Format-Table TimeCreated, Message -Wrap
}
else {
Write-Host "No DSC errors found in event log"
}
Common Issues and Solutions
Configuration Not Applying:
- Verify LCM is not in a pending state:
Get-DscLocalConfigurationManager | Select-Object LCMState - Check for resource conflicts or missing dependencies
- Ensure proper credentials for remote operations
Resource Not Found:
- Verify the resource module is installed:
Get-DscResource -Name ResourceName - Check module path and version compatibility
- Import the resource explicitly in your configuration
MOF Compilation Errors:
- Validate syntax in your configuration script
- Ensure all required parameters are provided
- Check for circular dependencies between resources
DSC Best Practices
Design Principles
- Idempotence: Ensure configurations can be applied multiple times without adverse effects
- Modularity: Break complex configurations into smaller, reusable components
- Version Control: Store DSC configurations in source control systems
- Testing: Test configurations in non-production environments first
- Documentation: Document parameters, dependencies, and expected outcomes
Security Considerations
# Use credentials securely
Configuration SecureConfig {
param (
[Parameter(Mandatory)]
[PSCredential]$ServiceCredential
)
Import-DscResource -ModuleName PSDesiredStateConfiguration
Node localhost {
Service SecureService {
Name = 'MyService'
Credential = $ServiceCredential
State = 'Running'
StartupType = 'Automatic'
}
}
}
# Never hardcode credentials - use parameter or certificate-based authentication
$cred = Get-Credential
SecureConfig -ServiceCredential $cred -OutputPath "C:\DSC\Secure"
Performance Optimization
- Use
DependsOnto control execution order and reduce unnecessary checks - Implement proper Test-TargetResource logic to avoid redundant Set operations
- Adjust LCM refresh frequencies based on environment needs
- Leverage partial configurations for large-scale deployments
Real-World DSC Scenarios
Multi-Tier Application Deployment
Configuration ThreeTierApp {
param (
[Parameter(Mandatory)]
[hashtable]$ConfigurationData
)
Import-DscResource -ModuleName PSDesiredStateConfiguration
Node $AllNodes.Where{$_.Role -eq 'WebServer'}.NodeName {
WindowsFeature IIS {
Name = 'Web-Server'
Ensure = 'Present'
}
WindowsFeature AspNet45 {
Name = 'Web-Asp-Net45'
Ensure = 'Present'
}
File WebApp {
SourcePath = '\\FileShare\WebApp'
DestinationPath = 'C:\inetpub\wwwroot'
Type = 'Directory'
Recurse = $true
Ensure = 'Present'
}
}
Node $AllNodes.Where{$_.Role -eq 'AppServer'}.NodeName {
WindowsFeature DotNet {
Name = 'NET-Framework-45-Core'
Ensure = 'Present'
}
Service AppService {
Name = 'MyApplicationService'
State = 'Running'
StartupType = 'Automatic'
}
File AppConfig {
DestinationPath = 'C:\Program Files\MyApp\config.json'
Contents = ConvertTo-Json $Node.AppSettings
Ensure = 'Present'
}
}
Node $AllNodes.Where{$_.Role -eq 'Database'}.NodeName {
WindowsFeature SQLEngine {
Name = 'SQL-Server-Full'
Ensure = 'Present'
}
Service MSSQLSERVER {
Name = 'MSSQLSERVER'
State = 'Running'
StartupType = 'Automatic'
}
}
}
# Configuration data defining the architecture
$appConfigData = @{
AllNodes = @(
@{
NodeName = 'WebServer01'
Role = 'WebServer'
},
@{
NodeName = 'WebServer02'
Role = 'WebServer'
},
@{
NodeName = 'AppServer01'
Role = 'AppServer'
AppSettings = @{
DatabaseConnection = 'Server=DBServer01;Database=AppDB'
CacheServer = 'redis.contoso.com'
}
},
@{
NodeName = 'DBServer01'
Role = 'Database'
}
)
}
ThreeTierApp -ConfigurationData $appConfigData -OutputPath "C:\DSC\ThreeTier"
DSC vs Configuration Management Alternatives
While DSC is native to Windows environments, understanding how it compares to other tools helps you choose the right solution:
- DSC vs Ansible: DSC offers deeper Windows integration and is agentless using WinRM, while Ansible provides broader cross-platform support
- DSC vs Chef: DSC uses declarative PowerShell syntax familiar to Windows admins, while Chef requires Ruby knowledge
- DSC vs Puppet: DSC leverages existing Windows infrastructure, while Puppet provides more mature enterprise features and reporting
- DSC vs Group Policy: DSC provides more granular control and better drift detection than traditional Group Policy
Integration with Azure Automation DSC
Azure Automation DSC extends on-premises DSC capabilities to the cloud:
# Register a node with Azure Automation DSC
$automationAccount = 'MyAutomationAccount'
$resourceGroup = 'MyResourceGroup'
$registrationUrl = (Get-AzAutomationRegistrationInfo -ResourceGroupName $resourceGroup `
-AutomationAccountName $automationAccount).Endpoint
$registrationKey = (Get-AzAutomationRegistrationInfo -ResourceGroupName $resourceGroup `
-AutomationAccountName $automationAccount).PrimaryKey
# Configure LCM for Azure Automation
[DSCLocalConfigurationManager()]
Configuration AzureAutomationConfig {
Node localhost {
Settings {
RefreshMode = 'Pull'
RefreshFrequencyMins = 30
ConfigurationMode = 'ApplyAndMonitor'
}
ConfigurationRepositoryWeb AzureAutomation {
ServerURL = $registrationUrl
RegistrationKey = $registrationKey
}
ReportServerWeb AzureAutomation {
ServerURL = $registrationUrl
RegistrationKey = $registrationKey
}
}
}
AzureAutomationConfig -OutputPath "C:\DSC\Azure"
Set-DscLocalConfigurationManager -Path "C:\DSC\Azure" -Verbose
Conclusion
PowerShell Desired State Configuration provides a robust framework for managing infrastructure as code. By defining configurations declaratively, you ensure consistency across environments, reduce configuration drift, and automate complex deployment scenarios. Whether managing a handful of servers or thousands of nodes, DSC scales to meet your infrastructure automation needs.
The key to successful DSC implementation lies in understanding core concepts like resources, configurations, and the Local Configuration Manager. Start with simple configurations, leverage built-in resources, and gradually build more sophisticated solutions as your expertise grows. Combined with proper testing, version control, and monitoring, DSC becomes an invaluable tool in your DevOps toolkit.








