Peter Hinchley

Learning in Public

✪ Setting up a PowerShell Desired State Configuration Pull Server

In this post I will show how you can deploy a PowerShell Desired State Configuration (DSC) Pull Server on Windows Server 2016.

I am assuming you already have two systems deployed with Windows Server 2016. The first system, which we will configure as the Pull Server, is running IIS and has been assigned a valid web server certificate (e.g. from an enterprise certificate authority). The second system will be used as a DSC client.

Let's start by installing NuGet on the Pull Server:

Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force

Next, we will trust the PSGallery repository:

Set-PSRepository -Name PSGallery -InstallationPolicy Trusted

Let's list all the modules available within the DSCResourceKit:

Find-Module -Tag DSCResourceKit

You should see the xPSDesiredStateConfiguration module. We will use this module to configure the Pull Server. Let's install it:

Install-Module xPSDesiredStateConfiguration

With that out of the way, let's create C:\DSC\PullServer\PullServer.ps1 with the following content:

configuration DscPullServer {
  param ( 
    [string[]]$NodeName = 'localhost',

    [ValidateNotNullOrEmpty()]
    [string] $certificateThumbPrint,

    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string] $RegistrationKey
  )

  Import-DSCResource –ModuleName PSDesiredStateConfiguration
  Import-DSCResource -ModuleName xPSDesiredStateConfiguration

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

    xDscWebService PSDSCPullServer {
      Ensure                   = 'Present'
      EndpointName             = 'PSDSCPullServer'
      Port                     = 4430
      PhysicalPath             = "$env:SystemDrive\inetpub\PSDSCPullServer"
      CertificateThumbPrint    = $certificateThumbPrint
      ModulePath               = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules"
      ConfigurationPath        = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration"
      State                    = 'Started'
      DependsOn                = '[WindowsFeature]DscServiceFeature'
      UseSecurityBestPractices = $false
    }

    File RegistrationKeyFile {
      Ensure          = 'Present'
      Type            = 'File'
      DestinationPath = "$env:ProgramFiles\WindowsPowerShell\DscService\RegistrationKeys.txt"
      Contents        = $RegistrationKey
    }
  }
}

The above configuration document outlines the configuration of the Pull Server. In particular, it will be used to create a new web site in IIS named PSDSCPullServer that listens on port 4430.

To invoke the configuration we will need to specify the thumbprint of the web server certificate already deployed to the server. You can see the thumbprints of the available certificates using the following command:

dir Cert:\LocalMachine\my

You will also need to generate a GUID that will be used as the unique key for registering new clients. The key provided by clients during the registration process must match this value - which is stored on the Pull Server in C:\Program Files\WindowsPowerShell\DscService\RegistrationKeys.txt. To generate a new GUID, run the following command:

$key = [guid]::newGuid()

We can now dot-source the configuration script, and invoke the configuration as follows (where $cert is set to the thumbprint of the web server certificate):

cd C:\DSC\PullServer
. .\DscPullServer.ps1
DscPullServer -certificateThumbprint $cert -RegistrationKey $key -OutputPath C:\DSC\PullServer
Start-DscConfiguration -Path . -Wait -Verbose

If everything went to plan, the Pull Server will be successfully configured.

We will now move onto the client configuration. There are two parts to this process. First, we will create a configuration document, implemented as a role-based partial named DefaultServer, that will leverage the File resource to ensure that C:\Temp exists. We will then create another document to setup the Local Configuration Manager (LCM) on the target (which will force it to register with the Pull Server).

To create the configuration role, add the following content to C:\DSC\DefaultServer\DefaultServer.ps1:

Configuration DefaultServer {
  Import-DscResource -ModuleName PSDesiredStateConfiguration
  Node DefaultServer {
    File TempDirectoryExists {
      Ensure          = 'Present'
      Type            = 'Directory'
      DestinationPath = 'C:\Temp'
    }
  }
}

Now dot-source the configuration, compile it, and deploy the resultant MOF (and the associated checksum) to the Pull Server configuration folder:

cd C:\DSC\DefaultServer
. .\DefaultServer.ps1
DefaultServer
New-DSCChecksum .\DefaultServer
Copy-Item .\DefaultServer\* "C:\Program Files\WindowsPowerShell\DscService\Configuration" -Force

Now create the LCM configuration document C:\DSC\DefaultServer\DefaultServer.ps1 with the following content:

[DSCLocalConfigurationManager()]
Configuration LCMMetaConfig {
  param (
    [string[]] $ComputerName = 'localhost',

    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string] $RegistrationKey
  )

  node $ComputerName {
    Settings {
      RefreshMode                    = 'Pull'
      ConfigurationMode              = 'ApplyAndMonitor'
      ActionAfterReboot              = 'ContinueConfiguration'
      RebootNodeIfNeeded             = $false
      ConfigurationModeFrequencyMins = '15'
      RefreshFrequencyMins           = '30'
      AllowModuleOverwrite           = $true
    }

    ConfigurationRepositoryWeb PullServerConfig {
      ServerURL               = 'https://dsc.lab.hinchley.net:4430/PSDSCPullServer.svc'
      AllowUnsecureConnection = $false
      RegistrationKey         = $RegistrationKey
      ConfigurationNames      = @('DefaultServer')
    }

    ReportServerWeb ReportServerConfig {
      ServerURL               = 'https://dsc.lab.hinchley.net:4430/PSDSCPullServer.svc'
      AllowUnsecureConnection = $false
      RegistrationKey         = $RegistrationKey
    }

    PartialConfiguration DefaultServer {
      Description         = 'DefaultServer'
      ConfigurationSource = @('[ConfigurationRepositoryWeb]PullServerConfig')
      RefreshMode         = 'Pull' 
    }
  }
}

The above document will configure LCM to use the Pull Server dsc.lab.hinchley.net, assign the DefaultServer partial, and enable remediation of configuration drift (via the ApplyAndAutoCorrect mode).

To compile the configuration, we need to supply the name of the target system (which in my case is named WINSTON), and the previously defined registration key:

cd C:\DSC\DefaultServer
. .\LCMMetaConfig.ps1
LCMMetaConfig -ComputerName WINSTON -RegistrationKey $key

And to deploy the configuration to WINSTON:

Set-DscLocalConfigurationManager -ComputerName WINSTON -Path '.\LCMMetaConfig' -Verbose

This command should initiate the registration of the target system with the Pull Server. The client will then download and apply the DefaultServer configuration, which will force the creation of C:\Temp (if it doesn't already exist). The client will reapply the configuration every 15 minutes (recreating C:\Temp if necessary), and poll the Pull Server for policy updates every 30 minutes.

To check the configuration has applied successfully, run the following command on the client:

 Get-DSCLocalConfigurationManager

The PartialConfigurations property should be set to {[PartialConfiguration]DefaultServer}.

And should you ever want to force the existing policy to be applied, run the following from the client:

Start-DscConfiguration –UseExisting –Verbose –Wait