How to Configure a DSC Pull Server on Windows Server 2012 R2

Desired State Configuration (DSC) is a PowerShell-based configuration management platform built into Windows Server 2012 R2. In push mode, an administrator pushes configurations directly to individual nodes. In pull mode, nodes periodically contact a central Pull Server to retrieve their configuration and report compliance status. A Pull Server enables at-scale configuration management across hundreds of servers without requiring administrative access to each node at configuration time. This guide walks through deploying a DSC Pull Server with HTTP and compliance reporting.

Prerequisites

– Windows Server 2012 R2 with PowerShell 4.0 or higher
– IIS role installed (Pull Server uses IIS or SMB file share)
– xPSDesiredStateConfiguration DSC resource module
– Certificates for HTTPS (recommended for production) or HTTP for lab
– Network access from all managed nodes to the Pull Server on port 8080 (HTTP) or 443 (HTTPS)
– Administrative credentials on the Pull Server

Step 1: Install Required DSC Resource Modules

The Pull Server configuration requires the xPSDesiredStateConfiguration module from the PowerShell Gallery or DSC Resource Kit:

# Install NuGet provider first
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force

# Install the xPSDesiredStateConfiguration module
Install-Module -Name xPSDesiredStateConfiguration -Force
Get-DSCResource -Module xPSDesiredStateConfiguration | Select-Object Name

# Also install the IIS role on the future Pull Server
Install-WindowsFeature -Name Web-Server, Web-Mgmt-Tools, Web-Default-Doc,
    Web-Dir-Browsing, Web-Http-Errors, Web-Static-Content,
    Web-Windows-Auth, Web-ISAPI-Ext, Web-ISAPI-Filter,
    Web-Net-Ext45, Web-ASP-Net45, Web-Log-Libraries -IncludeManagementTools

Step 2: Create the DSC Pull Server Configuration

Use a DSC configuration to bootstrap the Pull Server itself. This is a meta-bootstrap: you apply DSC to configure DSC:

Configuration DSCPullServer {
    param(
        [string]$NodeName = 'localhost',
        [string]$CertThumbprint = 'AllowUnencryptedTraffic',  # Replace with cert thumbprint in prod
        [int]$Port = 8080
    )

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

    Node $NodeName {

        WindowsFeature DSCServiceFeature {
            Ensure = "Present"
            Name   = "DSC-Service"
        }

        WindowsFeature WebServer {
            Ensure = "Present"
            Name   = "Web-Server"
            IncludeAllSubFeature = $false
        }

        xDscWebService PullServerHTTP {
            Ensure                  = "Present"
            EndpointName            = "DSCPullServer"
            Port                    = $Port
            PhysicalPath            = "$env:SystemDriveinetpubPSDSCPullServer"
            CertificateThumbPrint   = $CertThumbprint
            ModulePath              = "$env:PROGRAMFILESWindowsPowerShellDscServiceModules"
            ConfigurationPath       = "$env:PROGRAMFILESWindowsPowerShellDscServiceConfiguration"
            State                   = "Started"
            DatabasePath            = "$env:PROGRAMFILESWindowsPowerShellDscService"
            RegistrationKeyPath     = "$env:PROGRAMFILESWindowsPowerShellDscService"
            AcceptSelfSignedCertificates = $true
            UseSecurityBestPractices = $false  # Set to $true with proper TLS cert
            DependsOn               = "[WindowsFeature]DSCServiceFeature","[WindowsFeature]WebServer"
        }

        xDscWebService ComplianceServer {
            Ensure                  = "Present"
            EndpointName            = "DSCComplianceServer"
            Port                    = 9080
            PhysicalPath            = "$env:SystemDriveinetpubPSDSCComplianceServer"
            CertificateThumbPrint   = $CertThumbprint
            IsComplianceServer      = $true
            State                   = "Started"
            DependsOn               = "[xDscWebService]PullServerHTTP"
        }
    }
}

# Compile the MOF
DSCPullServer -NodeName "dscpull01" -OutputPath "C:DSCPullServerMOF"

# Apply it
Start-DscConfiguration -Path "C:DSCPullServerMOF" -Wait -Verbose -Force

Step 3: Create a Registration Key

Nodes authenticate to the Pull Server using a shared registration key (GUID). Create this file on the Pull Server:

# Generate a GUID to use as the registration key
$registrationKey = [System.Guid]::NewGuid().ToString()
Write-Host "Registration Key: $registrationKey"

# Save to the required location
$keyPath = "$env:PROGRAMFILESWindowsPowerShellDscServiceRegistrationKeys.txt"
$registrationKey | Out-File $keyPath -Encoding ASCII -Force

# Record this key — nodes need it during LCM configuration
# Example: a1b2c3d4-e5f6-7890-abcd-ef1234567890

Step 4: Publish a Node Configuration to the Pull Server

Write a DSC configuration for a node, compile it to MOF, generate a checksum, and copy it to the Pull Server’s configuration repository:

# Example node configuration: enforce that IIS is installed
Configuration WebServerBaseline {
    param([string[]]$NodeName)

    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"
        }
    }
}

# Compile — the output MOF file will be named after the NodeName
# For Pull mode, the MOF must be renamed to the node's ConfigurationID (a GUID)
WebServerBaseline -NodeName "web01.corp.local" -OutputPath "C:DSCConfigs"

# Rename to the node's ConfigurationID
$nodeConfigID = "5cf20a8b-1234-5678-abcd-ef0123456789"  # Must match LCM setting on node
Copy-Item "C:DSCConfigsweb01.corp.local.mof" `
    "C:DSCConfigs$nodeConfigID.mof"

# Generate checksum
New-DscChecksum -Path "C:DSCConfigs$nodeConfigID.mof" -Force

# Copy to Pull Server configuration store
$pullConfigPath = "$env:PROGRAMFILESWindowsPowerShellDscServiceConfiguration"
Copy-Item "C:DSCConfigs$nodeConfigID.mof"      $pullConfigPath
Copy-Item "C:DSCConfigs$nodeConfigID.mof.checksum" $pullConfigPath

Step 5: Publish DSC Resource Modules to Pull Server

Nodes require any DSC resource modules referenced in configurations to be available on the Pull Server. Package and publish them:

$modulesPath = "$env:PROGRAMFILESWindowsPowerShellDscServiceModules"

# Publish a DSC resource module (xWebAdministration example)
# First, find the module locally
$module = Get-Module xWebAdministration -ListAvailable | Select-Object -First 1

# Package as a zip
$zipName = "$($module.Name)_$($module.Version).zip"
Compress-Archive -Path $module.ModuleBase -DestinationPath "$modulesPath$zipName" -Force

# Generate checksum
New-DscChecksum -Path "$modulesPath$zipName" -Force

# Verify the module store
Get-ChildItem $modulesPath | Format-Table Name, Length -AutoSize

Step 6: Test the Pull Server Endpoint

Verify the Pull Server is responding correctly before pointing nodes at it:

# Test the Pull Server action handler
$pullServerURL = "http://dscpull01.corp.local:8080"

# Check the web service is responding
Invoke-WebRequest -Uri "$pullServerURL/PSDSCPullServer.svc" -UseBasicParsing |
    Select-Object StatusCode, StatusDescription

# Verify configuration endpoint
Invoke-WebRequest -Uri "$pullServerURL/PSDSCPullServer.svc/Action(ConfigurationId='$nodeConfigID')/ConfigurationContent" `
    -UseBasicParsing -Headers @{"ProtocolVersion"="2.0"} |
    Select-Object StatusCode

# Check IIS application pool is running
Get-WebConfiguration "system.applicationHost/applicationPools/add[@name='PSWS']" |
    Select-Object name, state

# Check Windows Event Log for DSC errors
Get-WinEvent -LogName "Microsoft-Windows-Desired State Configuration/Operational" -MaxEvents 20 |
    Where-Object { $_.Level -le 3 } |
    Format-Table TimeCreated, LevelDisplayName, Message -Wrap

Step 7: Firewall Rules for Pull Server

# Allow inbound connections to Pull Server ports
New-NetFirewallRule -DisplayName "DSC Pull Server HTTP" `
    -Direction Inbound -Protocol TCP -LocalPort 8080 `
    -Action Allow -Profile Domain

New-NetFirewallRule -DisplayName "DSC Compliance Server HTTP" `
    -Direction Inbound -Protocol TCP -LocalPort 9080 `
    -Action Allow -Profile Domain

# For HTTPS Pull Server (production)
New-NetFirewallRule -DisplayName "DSC Pull Server HTTPS" `
    -Direction Inbound -Protocol TCP -LocalPort 443 `
    -Action Allow -Profile Domain

Verification and Health Check

# Comprehensive Pull Server health check
function Test-DSCPullServer {
    param([string]$PullServerFQDN, [int]$Port = 8080)

    $baseURL = "http://${PullServerFQDN}:${Port}"

    # Test 1: Web service
    try {
        $r = Invoke-WebRequest "$baseURL/PSDSCPullServer.svc" -UseBasicParsing
        Write-Host "Web service: OK ($($r.StatusCode))" -ForegroundColor Green
    } catch {
        Write-Host "Web service: FAILED - $_" -ForegroundColor Red
    }

    # Test 2: Configuration folder
    $configPath = "$env:PROGRAMFILESWindowsPowerShellDscServiceConfiguration"
    $configCount = (Get-ChildItem $configPath -Filter "*.mof").Count
    Write-Host "Published configurations: $configCount" -ForegroundColor Cyan

    # Test 3: Module folder
    $modulePath = "$env:PROGRAMFILESWindowsPowerShellDscServiceModules"
    $moduleCount = (Get-ChildItem $modulePath -Filter "*.zip").Count
    Write-Host "Published modules: $moduleCount" -ForegroundColor Cyan

    # Test 4: Registration key
    $keyFile = "$env:PROGRAMFILESWindowsPowerShellDscServiceRegistrationKeys.txt"
    if (Test-Path $keyFile) {
        Write-Host "Registration key: Present" -ForegroundColor Green
    } else {
        Write-Host "Registration key: MISSING" -ForegroundColor Red
    }
}

Test-DSCPullServer -PullServerFQDN "dscpull01.corp.local"

Summary

A DSC Pull Server on Windows Server 2012 R2 provides the central configuration management backbone for a Windows infrastructure. By deploying the IIS-based pull service, publishing compiled MOF configurations with checksums, packaging DSC resource modules, and generating registration keys, you create a scalable platform where nodes self-manage their configuration state. Nodes periodically pull their configuration, apply it if drift is detected, and report compliance status to the reporting endpoint — all without requiring an administrator to interact with each machine individually. This approach is the foundation for infrastructure-as-code on Windows Server.