
September 11, 2026
14 min read
By Kokil Thapa | Last reviewed: September 2026
PowerShell Desired State Configuration (DSC) is Microsoft's declarative configuration engine for Windows. You describe how a node should look — roles installed, registry keys set, services running — and the Local Configuration Manager (LCM) applies that state repeatedly until reality matches the declaration. If you already automate Linux with Ansible or manage idempotent configuration management elsewhere, DSC is the native Windows equivalent baked into the PowerShell ecosystem. This guide walks through classic MOF-based DSC, push and pull delivery, Azure Automation, the newer DSC v3 direction, and the production mistakes I've seen on mixed Windows/Linux estates.
What is PowerShell Desired State Configuration and how does it work?
DSC separates what you want from how to get there. You write a configuration block. PowerShell compiles it into a Managed Object Format (MOF) document. The LCM on the target machine reads the MOF and invokes DSC resources — built-in or custom — until the node reports compliance.
That loop is the same idea behind Puppet manifests or Ansible playbooks. The difference is the runtime: DSC ships with Windows Management Framework and uses WMI/CIM under the hood on classic Windows nodes. On a production Windows Server you might run IIS, SQL, and file shares on the same box. DSC gives you one auditable declaration instead of a pile of one-off scripts.
The main moving parts are:
- Configuration author — a `.ps1` file with a
Configuration { }block and resource stanzas insideNode { }. - MOF output — compiled per-node documents the LCM consumes.
- Resources — small providers with
Get,Set, andTestsemantics (Script, Registry, WindowsFeature, File, etc.). - LCM — the agent on each node that schedules and applies configurations.
Classic DSC runs on Windows PowerShell 5.1 (WMF 5.1). That version still powers a large share of on-prem Windows Server estates in 2026. Microsoft also ships DSC v3 as a cross-platform direction without the traditional MOF/LCM pipeline — covered later in this article.
If your team already uses PowerShell automation for Windows servers, DSC is the natural upgrade path when scripts become hard to reason about. Scripts run once. DSC runs on a schedule and fights drift.
How do you write a basic PowerShell DSC configuration?
Start with a single-node push configuration. The pattern is always the same: import the module, define resources inside a Node block, compile, then apply.
Step 1: Author the configuration
# File: C:\DSC\WebServer.ps1
Configuration WebServer {
Import-DscResource -ModuleName PsDesiredStateConfiguration
Node 'localhost' {
WindowsFeature WebServerRole {
Name = 'Web-Server'
Ensure = 'Present'
}
Service W3SVC {
Name = 'W3SVC'
State = 'Running'
StartupType = 'Automatic'
DependsOn = '[WindowsFeature]WebServerRole'
}
File SiteRoot {
DestinationPath = 'C:\inetpub\wwwroot\index.html'
Contents = '<h1>Hello from DSC</h1>'
Type = 'File'
Ensure = 'Present'
DependsOn = '[WindowsFeature]WebServerRole'
}
}
} Step 2: Compile to MOF
PS C:\DSC> . .\WebServer.ps1
PS C:\DSC> WebServer -OutputPath C:\DSC\WebServer
Compilation creates a folder with a localhost.mof file. That MOF is the contract the LCM enforces.
Step 3: Apply with push mode
PS C:\DSC> Start-DscConfiguration -Path C:\DSC\WebServer -Wait -Verbose -Force
PS C:\DSC> Test-DscConfiguration
PS C:\DSC> Get-DscConfiguration Test-DscConfiguration returns True when the node matches the MOF. Get-DscConfiguration shows the actual values the resources read back. Use both during troubleshooting — a failed test with verbose logging usually points to a missing Windows feature or a permission problem on the destination path.
Step 4: Parameterise for multiple nodes
For a small fleet, pass a node list at compile time:
Configuration WebServer {
param(
[Parameter(Mandatory)]
[string[]]$NodeName
)
Import-DscResource -ModuleName PsDesiredStateConfiguration
Node $NodeName {
WindowsFeature WebServerRole {
Name = 'Web-Server'
Ensure = 'Present'
}
}
}
WebServer -NodeName 'WEB01','WEB02' -OutputPath C:\DSC\WebServer Then push each MOF remotely with PowerShell remoting over WinRM:
Invoke-Command -ComputerName WEB01,WEB02 -ScriptBlock {
Start-DscConfiguration -Path \\fileserver\dsc\WebServer -Wait -Verbose
} Validate JSON payloads or exported settings with a JSON formatter when you integrate DSC output into CI pipelines or Azure Automation.
What is the difference between push and pull mode in DSC?
Push mode is manual or CI-driven. You compile MOF and call Start-DscConfiguration against each node. Pull mode inverts control. Each node's LCM reaches out to a pull server (or Azure Automation) on a schedule, downloads fresh MOF and modules, and applies them without you targeting machines one by one.
Configure the LCM for pull mode
Each node needs meta-configuration — a special MOF that tells the LCM where to pull from and how often to check consistency:
[DSCLocalConfigurationManager()]
Configuration LcmPull {
Node 'localhost' {
Settings {
RefreshMode = 'Pull'
RefreshFrequencyMins = 30
RebootNodeIfNeeded = $true
ConfigurationMode = 'ApplyAndAutoCorrect'
ConfigurationModeFrequencyMins = 15
}
ConfigurationRepository WebPullServer {
ServerURL = 'https://pull.example.com/PSDSCPullServer.svc'
AllowUnsecureConnection = $false
}
}
}
LcmPull -OutputPath C:\DSC\LcmPull
Set-DscLocalConfigurationManager -Path C:\DSC\LcmPull ApplyAndAutoCorrect is the production default. The LCM re-applies desired state when someone drifts a setting manually. ApplyAndMonitor only reports drift — useful during migration when you are not ready for automatic remediation.
Azure Automation State Configuration
For teams without appetite to host their own pull server, Azure Automation State Configuration registers nodes, stores MOF, and reports compliance centrally. You compile locally or in CI, import the MOF to Azure, and register nodes with a registration key. The portal shows compliant vs non-compliant machines — similar to what you'd expect from a Puppet or Ansible Tower dashboard, but native to Azure AD joined or hybrid Windows fleets.
How does PowerShell DSC compare to Ansible, Puppet, and Terraform?
DSC is not a provisioning tool. It does not replace Terraform for creating VMs or networks. It sits in the same lane as Puppet, Chef, or Ansible configuration tasks — enforcing state after infrastructure exists. Many teams use Terraform for provisioning and a config manager for drift control.
| Tool | Primary platform | Model | Agent required | Strength |
|---|---|---|---|---|
| PowerShell DSC (classic) | Windows | Declarative MOF + LCM | Yes (LCM built in) | Native Windows resources, deep OS integration |
| DSC v3 | Windows, Linux, macOS | Declarative YAML/JSON | Lightweight (dsc CLI) | Cross-platform, no MOF pipeline |
| Ansible | Cross-platform | Imperative tasks, idempotent modules | No (WinRM/SSH) | One tool for Linux and Windows, huge module library |
| Puppet | Cross-platform | Declarative catalog | Yes (puppet agent) | Mature reporting, large enterprise adoption |
| Terraform | Cloud APIs | Declarative infrastructure | No | Creates resources; does not manage OS drift well alone |
On estates I maintain that mix Ubuntu web servers with a handful of Windows boxes, Ansible often wins for uniformity. When the Windows footprint grows — Active Directory, IIS farms, SQL Always On — DSC earns its place because built-in resources like WindowsFeature, xActiveDirectory, and SqlServerDsc community modules map directly to Microsoft primitives.
DSC also aligns with how Linux system administration teams think about desired state. The vocabulary differs. The goal is identical: repeatable, auditable configuration that survives staff turnover.
For remote state patterns in Terraform — locking, backends, team workflows — see manage Terraform state safely. DSC solves a different problem, but the same discipline applies: version your declarations, review changes, and never apply untested MOF to production blindly.
What are common PowerShell DSC mistakes in production?
DSC looks simple in demos. Production breaks in predictable ways. These are the issues I watch for when onboarding Windows nodes to declarative management.
1. Skipping resource dependencies
DSC parallelises resource application where possible. Without DependsOn, a Service resource may run before the feature that installs it. Always chain dependent resources explicitly, as in the IIS example above.
2. Wrong LCM configuration mode during migration
Teams sometimes leave ApplyAndMonitor in production and wonder why manual changes persist. Flip to ApplyAndAutoCorrect only after a successful test window. Schedule reboots with RebootNodeIfNeeded = $true when installing roles that require it.
3. Module version drift on pull servers
Pull mode serves both MOF and zip archives of resource modules. If WEB01 pulls NetworkingDsc 8.x and WEB02 still caches 7.x, you get silent inconsistency. Pin module versions in your pull server repository and bump them deliberately — the same discipline as locking Composer or npm versions on a web development project.
4. Secrets in plain MOF
MOF files are text. Never embed passwords directly. Use PSCredential with certificates:
Configuration SqlWithCredential {
param(
[Parameter(Mandatory)]
[PSCredential]$SqlCredential
)
Node 'SQL01' {
# resources referencing $SqlCredential
}
}
# Compile with encrypted credentials for target node cert thumbprint
SqlWithCredential -SqlCredential (Get-Credential) `
-OutputPath C:\DSC\Sql `
-ConfigurationData @{
AllNodes = @(
@{
NodeName = 'SQL01'
PSDscAllowPlainTextPassword = $false
CertificateFile = 'C:\Certs\SQL01.cer'
Thumbprint = 'ABC123...'
}
)
} Store the decryption certificate only on the target node. This pattern mirrors how you'd handle secrets in multi-server application configuration — never commit credentials to the artifact repository.
5. Treating DSC as a one-time installer
DSC is a continuous compliance engine. If you disable the consistency task or never monitor reports, drift returns within weeks. Wire compliance output into logging or Azure Monitor the same way you'd monitor fail2ban rules on Linux or disk alerts — configuration management without observability is half a solution.
Document every custom resource and third-party module. On an infrastructure project like SRP Infrastructure Development Nepal, the next engineer should open one repo and understand the full desired state — not reverse-engineer a pull server by hand.
When should you use DSC v3 instead of classic DSC?
Microsoft's classic DSC (WMF 5.1, MOF, LCM) remains valid for existing Windows Server fleets. DSC v3 is the forward path for new work in 2026. It drops the MOF/LCM pipeline in favour of a standalone dsc command-line tool, YAML or JSON configuration files, and cross-platform resources that run on Windows, Linux, and macOS through the same interface.
Choose classic DSC when:
- You maintain a large installed base already reporting to Azure Automation State Configuration.
- You depend on mature community resources like
ComputerManagementDsc,ActiveDirectoryDsc, orSqlServerDscthat target the MOF model. - Your runbooks and compliance tooling already ingest classic DSC events.
Choose DSC v3 when:
- You start a greenfield project and want one configuration language across Windows and Linux nodes.
- You prefer Git-tracked YAML over compiled MOF artifacts in CI.
- You want to invoke configuration from any shell or pipeline without installing the full LCM stack.
A minimal DSC v3 resource manifest is a JSON document describing parameters and the script that enforces state. Configurations reference resources by name. The exact schema evolves — always check the official DSC v3 overview on Microsoft Learn before pinning versions in CI.
DSC v3 does not magically replace Ansible on a mixed fleet overnight. It does give Microsoft-centric shops a credible exit from MOF complexity while keeping declarative semantics. Pair it with CI pipeline reviews so configuration changes get the same scrutiny as application code.
Custom resources in classic DSC
When built-in resources are not enough, author a class-based or MOF-based resource module with three functions: Get-TargetResource, Set-TargetResource, and Test-TargetResource. Test must be fast and side-effect free. Set makes changes. Get returns current state for reporting. That trio is the same contract Puppet types and providers use — once you internalise it, custom resources are straightforward.
# Simplified pattern inside a custom resource module
function Test-TargetResource {
param([string]$Path, [Ensure]$Ensure)
$exists = Test-Path $Path
if ($Ensure -eq 'Present') { return $exists }
return -not $exists
}
function Set-TargetResource {
param([string]$Path, [Ensure]$Ensure)
if ($Ensure -eq 'Present') { New-Item -Path $Path -ItemType Directory -Force }
else { Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue }
}
function Get-TargetResource {
param([string]$Path, [Ensure]$Ensure)
return @{ Path = $Path; Ensure = (Test-Path $Path) ? 'Present' : 'Absent' }
} Publish the module to your pull server or an internal NuGet/Artifactory feed. Version it SemVer-style. Breaking changes in a custom resource can reconfigure production IIS bindings or firewall rules on the next consistency pass — treat releases carefully.
Integrating DSC into a broader ops practice
DSC handles OS-level desired state. It does not replace backup verification, patch scheduling, or application deployment. On projects where I deliver support and maintenance, DSC covers the baseline: roles, security baselines, service states, registry hardening. Application releases still flow through GitLab CI, Deployer, or platform-specific pipelines.
If you also manage Windows DNS or hybrid networking, keep DNS declarations in separate configurations with their own test environments. A single monolithic configuration document becomes slow to compile and risky to change. Split by concern — web tier, data tier, domain controllers — and compose with configuration data files.
For enterprise rollouts, involve stakeholders early. Declarative management removes silent snowflake servers. That is good engineering and occasionally political friction. Document the compliance report dashboard before you enable auto-correct so teams know changes will be reverted.
Key Takeaways
- PowerShell Desired State Configuration declares Windows (and with v3, cross-platform) server state as code and enforces it through the LCM or the modern
dscCLI. - Author configurations in PowerShell, compile to MOF for classic DSC, and apply via push (
Start-DscConfiguration) or pull (LCM meta-configuration pointing at Azure or a pull server). - Use
ApplyAndAutoCorrect, certificate-encrypted credentials, explicitDependsOnchains, and pinned module versions before trusting DSC in production. - DSC complements — not replaces — Terraform for infrastructure creation and Ansible for homogeneous cross-platform tasks; pick based on fleet composition.
- Store configurations in Git, compile in CI, monitor compliance centrally, and evaluate DSC v3 for greenfield 2026 projects that need YAML-based declarative management.
- Custom resources follow Test/Set/Get semantics — keep
Testfast and idempotent to avoid side effects during consistency checks.
People Also Ask
Is PowerShell DSC still supported in 2026?
Yes. Classic DSC ships with Windows Management Framework 5.1 and remains supported on Windows Server through Microsoft's lifecycle for that platform. Azure Automation State Configuration continues to accept MOF-based configurations. Microsoft actively develops DSC v3 as the next-generation cross-platform engine documented on Microsoft Learn.
Do I need a pull server to use DSC?
No. Push mode applies MOF directly with Start-DscConfiguration and suits small fleets or CI-driven workflows. Pull mode — via a self-hosted pull server or Azure Automation — scales better when hundreds of nodes must fetch configuration and modules without manual targeting.
Can PowerShell DSC manage Linux servers?
Classic MOF-based DSC targeted Windows primarily, with limited Linux support through OMI-based setups that never saw wide adoption. DSC v3 is explicitly cross-platform and runs configuration resources against Linux and macOS through the same dsc tool chain. For large Linux estates today, Ansible remains the more common choice.
What is the difference between DSC and Group Policy?
Group Policy pushes Active Directory–linked settings to domain-joined clients on refresh intervals. DSC declares machine state — including roles, files, and services — and can run on workgroup or cloud VMs without AD dependency. Many enterprises use both: Group Policy for desktop baselines, DSC for server roles and Azure-hosted infrastructure.
Build declarative Windows baselines that survive drift
PowerShell Desired State Configuration turns ad-hoc server scripts into versioned, testable declarations. Start with one configuration — IIS, DNS, or a baseline hardening set — compile MOF, validate on a staging node, then expand to pull mode or Azure Automation as your fleet grows. Whether you run classic LCM-based DSC or pilot DSC v3 on new hosts, the payoff is the same: fewer snowflake servers and faster recovery when someone misclicks in Server Manager.
Need help designing configuration management across Windows and Linux estates, or integrating declarative baselines into your CI pipeline? Review our enterprise application development services or custom software development offerings, browse the portfolio for infrastructure work, and contact us to discuss your environment.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

