02 · Desired State Configuration (DSC) Basics¶
Everything so far has been imperative: your script says exactly how to do something, step by step. Desired State Configuration (DSC) flips that — you declare what the end state should look like (a file exists with this content, a service is running, a registry key is set), and a DSC engine figures out how to get there and keeps checking that it stays there.
A note on running this yourself
The configuration keyword needs the classic DSC engine, and on
Apple Silicon (ARM64) machines it refuses to load at all —
running any of the code below there raises Configuration keyword is
not supported on ARM64 processors. The examples in this module are
written and reviewed carefully for correctness, but were not executed
live for that reason — the same honest caveat this course applies to
Active Directory cmdlets in module 08. On an x64 Windows or Linux
machine with PowerShell 7 (or Windows PowerShell 5.1), everything here
runs as shown. Get-DscResource and Start-DscConfiguration are
Windows-only in practice; PowerShell 7's cross-platform DSC story is
Azure Policy guest configuration for Linux, which is out of scope here.
Declaring a configuration¶
configuration WebServerConfig {
Node "localhost" {
File ExampleFile {
DestinationPath = "C:\DscDemo\readme.txt"
Contents = "Managed by DSC"
Ensure = "Present"
}
}
}
WebServerConfig -OutputPath ./dscout
configuration is a PowerShell language keyword (not a function) that
compiles the block into one or more MOF files — one per Node — under
OutputPath. Node targets a specific machine by name; File is one of
the built-in DSC resources, describing the desired state of a single file.
Directory: C:\dscout
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 8/26/2026 10:02 AM 612 localhost.mof
Nothing has actually changed on disk yet — compiling a configuration only produces the MOF describing the target state.
Applying it¶
VERBOSE: [localhost]: LCM: [ Start Resource ] [[File]ExampleFile]
VERBOSE: [localhost]: LCM: [ Start Set ] [[File]ExampleFile]
VERBOSE: [localhost]: [[File]ExampleFile] The
configuration of file "C:\DscDemo\readme.txt" is not correct.
VERBOSE: [localhost]: [[File]ExampleFile] Setting
the contents of file "C:\DscDemo\readme.txt".
VERBOSE: [localhost]: LCM: [ End Set ] [[File]ExampleFile]
VERBOSE: [localhost]: LCM: [ End Resource ] [[File]ExampleFile]
The Local Configuration Manager (LCM) — the engine running on every
DSC-capable node — reads the MOF, checks current state against desired
state resource-by-resource, and only touches what's actually wrong. Run
Start-DscConfiguration again with no drift and every resource reports
"already in the desired state" — nothing changes on a second run.
Checking for drift without changing anything¶
InDesiredState ResourcesInDesiredState ResourcesNotInDesiredState
-------------- ----------------------- --------------------------
False {} {ExampleFile}
This is the read-only half of DSC's value: you can audit a fleet of machines for drift from their declared configuration without applying anything, then decide separately whether to remediate.
Parameters and multiple nodes¶
configuration WebServerConfig {
param(
[string[]]$ComputerName = "localhost",
[string]$Content = "Managed by DSC"
)
Node $ComputerName {
File ExampleFile {
DestinationPath = "C:\DscDemo\readme.txt"
Contents = $Content
Ensure = "Present"
}
WindowsFeature IIS {
Name = "Web-Server"
Ensure = "Present"
}
}
}
WebServerConfig -ComputerName "Web01", "Web02" -OutputPath ./dscout
Node $ComputerName iterates the array, emitting one MOF per machine
named — Web01.mof, Web02.mof — each independently deployable via
Start-DscConfiguration -ComputerName Web01,Web02.
The trap: DSC resources are declarative blocks, not statements¶
# Wrong instinct: this looks like it runs top-to-bottom like a script
configuration Bad {
Node "localhost" {
Service SvcA { Name = "Spooler"; State = "Running" }
Service SvcB { Name = "BITS"; State = "Running" }
}
}
Resources inside a Node block are not guaranteed to apply in the
order written unless you explicitly declare a dependency with
DependsOn:
configuration Ordered {
Node "localhost" {
WindowsFeature IIS {
Name = "Web-Server"
Ensure = "Present"
}
File SiteContent {
DestinationPath = "C:\inetpub\wwwroot\index.html"
Contents = "<h1>Hello</h1>"
Ensure = "Present"
DependsOn = "[WindowsFeature]IIS"
}
}
}
Without DependsOn, the LCM is free to write index.html before IIS is
even installed, creating the directory manually — order in the script is
not order of application. This is the single most common DSC bug for
people coming from imperative scripting.
Cheat sheet¶
| Concept | Purpose |
|---|---|
configuration { } |
declarative block compiled to one MOF per node |
Node "name" { } |
target a specific machine; one MOF file per node |
Built-in resources (File, Service, WindowsFeature, Registry) |
describe one piece of desired state |
-OutputPath |
where compiled .mof files land |
Start-DscConfiguration -Wait -Verbose |
apply a compiled configuration |
Test-DscConfiguration -Detailed |
audit drift without changing anything |
DependsOn = "[Type]Name" |
force one resource to apply after another |
Get-DscConfiguration |
show current state as last applied |
How It Actually Works¶
A DSC configuration block looks like a function call but compiles to
something categorically different: Configuration Foo { Node 'x' {
WindowsFeature ... } } is parsed by a special configuration keyword
handler that, instead of executing resource blocks as statements, builds a
declarative document — a MOF (Managed Object Format) file, the same
CIM/WMI schema format used across DMTF-standard system management. Running
the configuration function doesn't apply anything; it compiles your
declarative intent into .mof files (one per targeted node), which is
exactly why DSC configurations feel non-imperative — the block's body
describes desired end-state data, not a sequence of actions to run in
order.
Applying that MOF is a separate step (Start-DscConfiguration) that hands
the compiled document to the Local Configuration Manager (LCM), a
component that runs as a scheduled/background engine on the target and is
responsible for the actual reconciliation loop: for each resource entry in
the MOF, the LCM calls that DSC resource's Test-TargetResource function
first — if it reports the system already matches desired state, the LCM
skips straight to the next resource; only on a mismatch does it call
Set-TargetResource to make the change. This test-then-set pattern is
the real mechanism behind DSC's idempotency guarantee — resources are
required to implement Get/Test/Set precisely so the engine never
needs to guess whether a change is necessary.
Because the LCM itself keeps running (in ApplyAndMonitor/
ApplyAndAutoCorrect modes) on a periodic pull/push schedule, DSC
provides genuine configuration drift detection and correction — a change
made outside DSC's control (someone manually disabling the feature you
declared) gets detected and reverted on the LCM's next consistency pass,
something a one-shot imperative script has no built-in mechanism to do.
Exercise¶
Write a configuration block named DevBoxSetup that ensures: a
directory C:\DevTools exists (File resource with
Type = "Directory"), a Registry value flags the machine as
provisioned, and a Service of your choosing is running — with the
service declared to depend on the directory via DependsOn. Compile it
with -OutputPath and inspect the generated .mof file's contents (on a
non-ARM64 machine, or read through the logic by hand if you're on Apple
Silicon) to confirm the dependency ordering is captured correctly.