Declarative vs. Imperative Code
As a system administrator, you’ve likely mastered PowerShell to automate tasks and manage systems. But as your scripts grow in complexity, you might find they become brittle and hard to maintain. What if there was a better way to define your infrastructure’s state without micromanaging every step?
The secret lies in shifting from an imperative to a declarative mindset. This is a core principle of modern software development and DevOps that can make your automation more robust, predictable, and easier to read.
Imperative: Telling PowerShell “How”
When we start scripting, we usually write imperative code. We provide a detailed, step-by-step list of instructions for the computer to follow.
Think about ensuring a specific Windows service, like Spooler
, is running. An imperative script would look like this:
# Imperative Approach: "How" to do it
$serviceName = "Spooler"
$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($null -eq $service) {
Write-Warning "Service '$serviceName' does not exist. Cannot proceed."
}
else {
if ($service.Status -ne 'Running') {
Write-Host "Service '$serviceName' is stopped. Starting it now..."
Start-Service -Name $serviceName
Write-Host "Service '$serviceName' has been started."
}
else {
Write-Host "Service '$serviceName' is already running."
}
}
This works, but it’s verbose. We had to manually check for the service’s existence, check its state, and then decide what to do. We told PowerShell how to do the task, including all the conditional logic.
Declarative: Telling PowerShell “What”
Now, let’s look at the declarative approach. Instead of telling PowerShell how to do something, we declare what we want the final state to be. The system then figures out the “how” for us. In PowerShell, the primary tool for this is Desired State Configuration (DSC).
Here’s the same goal achieved with DSC:
# Declarative Approach: "What" we want
Configuration EnsureSpoolerIsRunning {
# Import the module that contains the Service resource
Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
Node 'localhost' {
Service 'SpoolerService' {
Name = 'Spooler'
State = 'Running' # We declare the desired state is 'Running'
Ensure = 'Present' # We declare the service should exist
}
}
}
# Now, we can apply this configuration
EnsureSpoolerIsRunning
Start-DscConfiguration -Path .\EnsureSpoolerIsRunning -Wait -Verbose
Notice the difference? We didn’t write any if/else
logic. We simply declared that we want a service named Spooler
to be present and in a Running
state. DSC handles the underlying logic to check the current state and only makes changes if the system isn’t in the desired state.
Why This Matters
This declarative model is incredibly powerful because it’s idempotent—you can run it a hundred times, and it will only perform an action the first time (or any time the state drifts). This makes your automation safer, more predictable, and easier to understand at a glance.
By embracing a declarative approach, you move from writing fragile scripts to defining robust, self-healing configurations. It’s the first major step in thinking less like a traditional scripter and more like a developer.