Manual configuration is a recipe for disaster at scale: the fifth machine is never the same as the first. This episode covers Desired State Configuration, configuration as code, push and pull modes, applying and detecting drift, built-in and custom resources, and setting up a pull server for large infrastructure.

In episode 25 you put scripts under version control and triggered automated tests. But notice: what you versioned is behavior, not machine state. The second server in a server farm is often different from the first — IIS installed on one, a service running on another, a config file manually changed by someone who no longer remembers. Manual configuration like this is a recipe for disaster: the fifth machine is never identical to the first.
Desired State Configuration (DSC) answers this with a radical idea: don't write step-by-step "how to configure a server", write the desired end state, and let the machine figure out how to reach it. This episode covers DSC as configuration as code, push and pull modes, writing configuration, applying and detecting drift, DSC resources, and setting up a pull server for large-scale infrastructure.
DSC is a PowerShell feature for configuration management: you describe a target machine's condition — which Windows features are enabled, which services are running, which files must exist — then DSC enforces it.
The difference from the imperative script approach is fundamental:
DSC decides how to reach that state, and most importantly: DSC re-checks periodically. If a service dies after DSC finishes, DSC will start it again. Imperative scripts don't do that — they finish and leave.
Note
Classic DSC runs on Windows PowerShell 5.1 with the Local Configuration Manager (LCM). In PowerShell 7, the DSC landscape is evolving — v2 and v3 introduce new approaches. To master the concepts, first understand DSC 5.1, which still dominates the field; the declarative-state and drift concepts apply across all generations.
DSC works in two modes:
DSC configuration is written as a Configuration block containing a Node block for each target machine, with resource declarations inside. The first configuration that installs IIS and starts its service:
Configuration WebServer {
Node "WEB-01" {
WindowsFeature IIS {
Name = "Web-Server"
Ensure = "Present"
}
Service W3Svc {
Name = "W3SVC"
State = "Running"
DependsOn = "[WindowsFeature]IIS"
}
}
}
WebServer -OutputPath "C:\DSC\WebServer"Note how to read it: the WindowsFeature block named IIS ensures the Web-Server feature is Present; the Service block ensures the W3SVC service is Running, and may only be done after the IIS feature finishes (see DependsOn). Running the WebServer function produces a folder containing MOF files — a target-state recipe movable to any machine.
Hardcoding node names in configuration makes it non-reusable. Separate data from logic using the -ConfigurationData parameter:
$configData = @{
AllNodes = @(
@{ NodeName = "WEB-01"; Role = "Web" }
@{ NodeName = "APP-01"; Role = "App" }
)
}
Configuration Servers {
Node $AllNodes.NodeName {
WindowsFeature FeatureIIS {
Name = "Web-Server"
Ensure = "Present"
}
}
}
Servers -ConfigurationData $configData -OutputPath "C:\DSC\Servers"Now one configuration serves many machines. Add a new property to the AllNodes array (for example swap size or a service name), reference it inside the configuration, and all machines follow — without changing the core logic. This is the essence of configuration as code: data and behavior are separated, and both go into git like ordinary code.
With the generated MOF files, apply to the target machine:
Start-DscConfiguration -Path "C:\DSC\WebServer" -Wait -Verbose
Test-DscConfiguration -Path "C:\DSC\WebServer"
Get-DscConfigurationStart-DscConfiguration applies configuration to the local machine (or with -ComputerName to other machines). -Wait waits until it finishes; -Verbose shows details.Test-DscConfiguration checks without changing: does the real state match the desired one?Get-DscConfiguration reads the currently applied state.When Test-DscConfiguration returns False, that means drift — the machine has deviated from the desired state. Perhaps a service was manually stopped, or a config file was edited outside DSC. Running Start-DscConfiguration again will repair the drift and return the machine to the described state. This is the main difference from one-time scripts: DSC doesn't just execute, it maintains.
DSC is driven by resources — units that know how to check and enforce one kind of state. Built-in resources include:
| Resource | Manages |
|---|---|
File | Existence and content of files or folders |
Service | Windows service state |
WindowsFeature | Windows features and roles |
Registry | Registry values |
Script | Custom code blocks with Get, Set, and Test |
User | Local user accounts |
Above the built-in resources is the community ecosystem — modules like PSDscResources and x* packages (for example xPSDesiredStateConfiguration) adding resources for networking, storage, and more. When none of that is enough, DSC supports custom resources: modules exposing Get, Set, and Test functions for any state you can imagine. That customizability makes DSC able to touch almost anything — as long as it can be described as a state.
On dozens or hundreds of machines, push mode is impractical: pressing configuration onto each machine one by one is work that never finishes. Pull mode changes the architecture: machines register and periodically pull the latest configuration from the pull server, then apply it themselves. Configuration is changed once on the server; all machines follow on the next cycle.
Node registration is managed via the LCM meta-configuration — configuration that configures DSC itself:
[DSCLocalConfigurationManager()]
Configuration PullClient {
Node "WEB-01" {
Settings {
RefreshMode = "Pull"
ConfigurationMode = "ApplyAndAutoCorrect"
RebootNodeIfNeeded = $true
}
ConfigurationRepositoryWeb PullServer {
ServerURL = "http://dsc-pull.example.com/PSDSCPullServer.svc"
RegistrationKey = "6b2c-f1a9-..."
}
}
}RefreshMode = "Pull" tells the node to pull configuration from the server; ConfigurationMode = "ApplyAndAutoCorrect" makes the machine check and repair drift periodically, not just when called. This is where DSC's power shows: not just automation waiting for a command, but machines that continuously keep themselves in the declared state.
In this episode 26 you've understood DSC as declarative configuration management: the difference between "how to do it" commands and "desired state" descriptions; two distribution modes, push and pull; writing Configuration and Node blocks with resource declarations; separating configuration data via AllNodes and -ConfigurationData; applying and checking with Start-DscConfiguration, Test-DscConfiguration, and Get-DscConfiguration; the concept of drift as automatically repairable deviation; built-in, community, and custom resources; and LCM meta-configuration for pull mode in large-scale infrastructure.
Key takeaways:
Test-DscConfiguration is the drift alarm; Start-DscConfiguration is the fix.-ConfigurationData.ApplyAndAutoCorrect make machines maintain themselves.Your machines can now be described, applied, and maintained. But there's one next step that makes scale real: running all of this from the cloud. In the next episode, episode 27, we cover Azure Automation with PowerShell — the Az module, resource group and VM management, Microsoft Graph for users and groups, and runbooks, schedules, and hybrid workers in Azure Automation. See you there!