How to Configure Desired State Configuration (DSC) Pull Server on Windows Server 2025

Desired State Configuration is PowerShell’s declarative system configuration framework. Rather than writing imperative scripts that install software and set registry keys step by step, you describe the end state you want — which Windows features should be installed, which services should be running, which files should exist — and DSC’s Local Configuration Manager (LCM) enforces that state on every managed node. A Pull Server extends this model to a fleet: nodes periodically check a central server for updated configurations and apply them automatically, with no administrator involvement after initial setup. This guide walks through building a complete DSC Pull Server on Windows Server 2025, publishing configurations, and configuring target nodes to pull automatically.

Prerequisites

  • Windows Server 2025 for both the pull server and target nodes
  • PowerShell 5.1 (DSC v1) — the built-in engine on Windows Server 2025
  • Administrator access on all servers
  • IIS available for HTTP/HTTPS pull mode, or a shared SMB path for file-based pull
  • Network connectivity between nodes and the pull server on port 8080 (HTTP) or 443 (HTTPS)
  • A valid SSL certificate for HTTPS deployments (strongly recommended for production)

Step 1: Install the DSC Service Role

The HTTP DSC Pull Server runs as an IIS web application powered by the DSC-Service Windows feature. Install it along with IIS on the pull server.

# On the pull server
Install-WindowsFeature -Name DSC-Service, Web-Server, Web-Mgmt-Tools -IncludeManagementTools

# Verify installation
Get-WindowsFeature DSC-Service, Web-Server | Format-Table Name, Installed, InstallState

Step 2: Configure the DSC Pull Server

DSC provides a built-in DSC configuration named xDscWebService (from the xPSDesiredStateConfiguration module) for setting up the pull server. This is meta-configuration: using DSC to configure the DSC infrastructure itself.

# Install the required module on the pull server
Install-Module xPSDesiredStateConfiguration -Force -AllowClobber
Import-DscResource -ModuleName xPSDesiredStateConfiguration

# DSC Configuration to set up the pull server
configuration PullServerSetup {
    param (
        [string]$NodeName = 'localhost',
        [string]$CertThumbprint = 'AllowUnencryptedTraffic',  # replace with real cert thumbprint
        [string]$RegistrationKey = (New-Guid).Guid
    )

    Import-DscResource -ModuleName xPSDesiredStateConfiguration
    Import-DscResource -ModuleName PSDesiredStateConfiguration

    Node $NodeName {
        WindowsFeature DSCServiceFeature {
            Ensure = 'Present'
            Name   = 'DSC-Service'
        }

        xDscWebService PSDSCPullServer {
            Ensure                  = 'Present'
            EndpointName            = 'PSDSCPullServer'
            Port                    = 8080
            PhysicalPath            = "$env:SystemDriveinetpubPSDSCPullServer"
            CertificateThumbPrint   = $CertThumbprint
            ModulePath              = "$env:PROGRAMFILESWindowsPowerShellDscServiceModules"
            ConfigurationPath       = "$env:PROGRAMFILESWindowsPowerShellDscServiceConfiguration"
            State                   = 'Started'
            RegistrationKeyPath     = "$env:PROGRAMFILESWindowsPowerShellDscService"
            AcceptSelfSignedCertificates = $true
            UseSecurityBestPractices = $false   # set true with valid cert
            DependsOn               = '[WindowsFeature]DSCServiceFeature'
        }

        File RegistrationKeyFile {
            Ensure          = 'Present'
            Type            = 'File'
            DestinationPath = "$env:PROGRAMFILESWindowsPowerShellDscServiceRegistrationKeys.txt"
            Contents        = $RegistrationKey
        }
    }
}

# Compile the configuration (creates localhost.mof)
PullServerSetup -OutputPath 'C:DSCPullServerMof'

# Note the registration key — target nodes need this
Write-Host "Registration Key: "

# Apply the configuration
Start-DscConfiguration -Path 'C:DSCPullServerMof' -Wait -Verbose -Force

After applying this configuration, IIS will host the DSC pull endpoint at http://pullserver:8080/PSDSCPullServer.svc.

Step 3: Write a Node Configuration

Create a DSC configuration that describes the desired state for a managed server. This example ensures IIS is installed, a specific service is running, and a configuration file exists.

# File: C:DSCConfigsWebServerConfig.ps1

configuration WebServerConfig {
    param (
        [string[]]$NodeName = 'WebServer01'
    )

    Import-DscResource -ModuleName PSDesiredStateConfiguration

    Node $NodeName {
        WindowsFeature IIS {
            Ensure = 'Present'
            Name   = 'Web-Server'
        }

        WindowsFeature IISManagement {
            Ensure    = 'Present'
            Name      = 'Web-Mgmt-Tools'
            DependsOn = '[WindowsFeature]IIS'
        }

        Service W3SVC {
            Name      = 'W3SVC'
            State     = 'Running'
            StartupType = 'Automatic'
            DependsOn = '[WindowsFeature]IIS'
        }

        File DefaultSitePage {
            Ensure          = 'Present'
            Type            = 'File'
            DestinationPath = 'C:inetpubwwwrootindex.html'
            Contents        = '

Managed by DSC

' DependsOn = '[WindowsFeature]IIS' } Registry MaxConnections { Ensure = 'Present' Key = 'HKLM:SYSTEMCurrentControlSetServicesHTTPParameters' ValueName = 'MaxConnections' ValueData = '16384' ValueType = 'DWord' } } } # Compile — produces WebServer01.mof WebServerConfig -OutputPath 'C:DSCCompiled'

Step 4: Generate Checksums and Publish to Pull Server

The pull server uses checksums to detect when a node’s configuration has changed. Every .mof file needs a matching .mof.checksum file. DSC resource modules published to the pull server need the same treatment.

$configRepo = "$env:PROGRAMFILESWindowsPowerShellDscServiceConfiguration"
$moduleRepo = "$env:PROGRAMFILESWindowsPowerShellDscServiceModules"

# Publish the compiled MOF
Copy-Item 'C:DSCCompiledWebServer01.mof' -Destination $configRepo

# Generate the checksum
New-DscChecksum -Path "$configRepoWebServer01.mof" -Force

# Verify both files exist
Get-ChildItem $configRepo | Format-Table Name, Length, LastWriteTime

# Publish a DSC resource module to the pull server
# Modules must be zipped with the correct naming: ModuleName_Version.zip
$moduleName    = 'xWebAdministration'
$moduleVersion = (Get-Module xWebAdministration -ListAvailable).Version.ToString()
$moduleSource  = "$env:PROGRAMFILESWindowsPowerShellModules$moduleName"
$zipName       = "${moduleName}_${moduleVersion}.zip"

Compress-Archive -Path $moduleSource -DestinationPath "$moduleRepo$zipName" -Force
New-DscChecksum -Path "$moduleRepo$zipName" -Force

Write-Host "Published $zipName to pull server module repository"

Step 5: Configure the LCM on Target Nodes

Each target node needs its LCM configured to pull from the pull server. The LCM configuration is itself a special DSC metaconfiguration compiled with the [DscLocalConfigurationManager()] attribute and applied with Set-DscLocalConfigurationManager.

# Run this on the target node or push via Invoke-Command
# ConfigurationID must match the MOF filename on the pull server (without .mof)

[DSCLocalConfigurationManager()]
configuration LCMPullConfig {
    param(
        [string]$NodeName = 'localhost',
        [string]$PullServerURL = 'http://PULLSERVER01:8080/PSDSCPullServer.svc',
        [string]$RegistrationKey = 'YOUR-REGISTRATION-KEY-GUID',
        [string]$ConfigurationID = 'WebServer01'
    )

    Node $NodeName {
        Settings {
            RefreshMode          = 'Pull'
            ConfigurationMode    = 'ApplyAndAutoCorrect'  # drift detection enabled
            RefreshFrequencyMins = 30
            RebootNodeIfNeeded   = $true
            AllowModuleOverwrite = $true
            ConfigurationID      = $ConfigurationID
        }

        ConfigurationRepositoryWeb PullServerWeb {
            ServerURL          = $PullServerURL
            RegistrationKey    = $RegistrationKey
            ConfigurationNames = @($ConfigurationID)
        }

        ResourceRepositoryWeb PullServerModules {
            ServerURL       = $PullServerURL
            RegistrationKey = $RegistrationKey
        }

        ReportServerWeb PullServerReporting {
            ServerURL       = $PullServerURL
            RegistrationKey = $RegistrationKey
        }
    }
}

# Compile and apply the LCM configuration
LCMPullConfig -OutputPath 'C:DSCLCMConfig'
Set-DscLocalConfigurationManager -Path 'C:DSCLCMConfig' -Verbose -Force

# Verify LCM settings
Get-DscLocalConfigurationManager | Format-List RefreshMode, ConfigurationMode, ConfigurationID

Step 6: Force and Monitor Configuration Application

# Trigger an immediate pull and apply cycle (don't wait for the 30-minute interval)
Update-DscConfiguration -Wait -Verbose

# Check current DSC status
Get-DscConfigurationStatus | Format-List

# Check for configuration drift
Test-DscConfiguration -Detailed

# View the last DSC event log entries
Get-WinEvent -LogName 'Microsoft-Windows-DSC/Operational' -MaxEvents 20 |
    Select-Object TimeCreated, Id, Message | Format-Table -Wrap -AutoSize

# On the pull server — view compliance reports submitted by nodes
# Reports are stored in the pull server's database
$reportPath = "$env:PROGRAMFILESWindowsPowerShellDscService"
Get-ChildItem $reportPath -Filter '*.edb'

Step 7: Azure Automation DSC as a Fully Managed Alternative

Running your own pull server means managing IIS, the database, and the server’s availability. Azure Automation DSC provides the same pull semantics as a fully managed service — no infrastructure to operate.

# Import a DSC configuration into Azure Automation
Import-AzAutomationDscConfiguration `
    -AutomationAccountName 'aa-production' `
    -ResourceGroupName 'rg-automation' `
    -SourcePath 'C:DSCConfigsWebServerConfig.ps1' `
    -Published `
    -Force

# Compile the configuration for a specific node
$params = @{ NodeName = 'WebServer01' }
Start-AzAutomationDscCompilationJob `
    -AutomationAccountName 'aa-production' `
    -ResourceGroupName 'rg-automation' `
    -ConfigurationName 'WebServerConfig' `
    -Parameters $params

# Register an Azure VM or Arc server as a DSC node
Register-AzAutomationDscNode `
    -AutomationAccountName 'aa-production' `
    -ResourceGroupName 'rg-automation' `
    -AzureVMName 'WS2025-WEB01' `
    -AzureVMResourceGroup 'rg-production' `
    -NodeConfigurationName 'WebServerConfig.WebServer01' `
    -ConfigurationMode 'ApplyAndAutoCorrect' `
    -RebootNodeIfNeeded $true

# Check node compliance status
Get-AzAutomationDscNode `
    -AutomationAccountName 'aa-production' `
    -ResourceGroupName 'rg-automation' |
    Select-Object Name, Status, LastSeen | Format-Table -AutoSize

Conclusion

A DSC Pull Server on Windows Server 2025 gives your organization an automated configuration enforcement layer that works continuously, not just at initial deployment. Nodes check in every 30 minutes by default, detect drift, and correct it without human intervention. The combination of compiled MOF files, published checksums, and LCM pull mode scales from a handful of servers to hundreds. For teams who prefer to avoid managing pull server infrastructure, Azure Automation DSC delivers the same compliance-driven model as a fully managed service with built-in reporting and integration with Azure Monitor. Either way, moving from imperative configuration scripts to DSC declarative configurations means your server states are documented, testable, version-controlled, and self-healing.