Learn PowerShell - Configuration Management (DSC)
Episode 26 of 31

Learn PowerShell - Configuration Management (DSC)

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.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

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.

What Is Desired State Configuration

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:

  • Imperative scripts say: "how to do it" — install IIS, then open port 80, then start the service.
  • Declarative DSC says: "the desired state" — IIS is installed, port 80 is open, the service is running.

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.

Push vs Pull

DSC works in two modes:

  • Push — you push configuration to one or more machines from your machine. Simple, suitable for a few machines or when just learning.
  • Pull — machines periodically fetch configuration from a pull server. New machines can configure themselves as soon as they register, and configuration updates spread automatically. Suitable for large scale.

Writing Configuration

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:

Your first DSC configuration
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.

Configuration Data

Hardcoding node names in configuration makes it non-reusable. Separate data from logic using the -ConfigurationData parameter:

Separated configuration data
$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.

Applying Configuration

With the generated MOF files, apply to the target machine:

Push, check, and read state
Start-DscConfiguration -Path "C:\DSC\WebServer" -Wait -Verbose
Test-DscConfiguration -Path "C:\DSC\WebServer"
Get-DscConfiguration
  • Start-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 Resources

DSC is driven by resources — units that know how to check and enforce one kind of state. Built-in resources include:

ResourceManages
FileExistence and content of files or folders
ServiceWindows service state
WindowsFeatureWindows features and roles
RegistryRegistry values
ScriptCustom code blocks with Get, Set, and Test
UserLocal 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.

Pull Server and Scale

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:

LCM meta-configuration for pull mode
[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.

Conclusion

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:

  • Describe the end state, let DSC enforce it — and maintain it.
  • Test-DscConfiguration is the drift alarm; Start-DscConfiguration is the fix.
  • Separate configuration data from logic with -ConfigurationData.
  • Pull mode and ApplyAndAutoCorrect make machines maintain themselves.
  • Configuration is code — put it in git and review it like code.

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!

Learn PowerShell - Configuration Management (DSC) | Learn PowerShell