Just another tool: getting to know the service configuration with Desired State Configuration 

Desired State Configuration (DSC) is a server configuration management tool. With it, you can configure the server (make changes to the registry, copy files, install and remove components), monitor the current status of the settings and quickly roll back to the basic settings.

DSC is interesting for those who follow the DevOps approach. This tool fits well with the paradigm of Infrastructure as a Code: developers can add their requirements to the configuration and include it in the version control system, and teams can deploy the code without using “manual” processes.

Together with Stanislav Buldakov from RaiffeisenbankWe combined our experience with the DSC engine and divided it into 2 articles. In the first, we will analyze the basic principles of work and get acquainted with the features of use on practical examples:

  • “Unpack the box” with the DSC engine, see what resources are by default, and show where to get additional resources;
  • Let's see how to describe the configuration in DSC; 
  • We will learn how the built-in agent Local Configuration Manager applies configurations on the server, show how it is configured using meta-configurations;
  • let's move on to more complex configuration cases: partial configurations and stub configurations.



DSC is a lightweight and fast engine. For example, using it, you can install the .NET Framework 3.5 on virtual machines much faster. Here's what helps him speed up the service configuration process:

  • « » Windows PowerShell. 
    DSC PowerShell Windows Management Framework. Linux . , PowerShell. 
  • , . 
    , , , . DSC. 
  • , .
    DSC , . .

Configurations are always performed sequentially, without conditions and branching. Therefore, briefly, the DSC operation algorithm looks like this:

  1. Configure Local Configuration Manager (LCM) . This is the built-in agent that is responsible for applying configurations to the server. We tell him how the declared configurations should work and in what order. 
  2. If additional resources are needed, install and connect them in advance.
  3. In a declarative form, we write the configuration order. 
  4. We broadcast the configuration in the format of the MOF file.
  5. We send the configuration to the target freshly deployed or existing server.
  6. LCM receives the configuration (MOF file) plus instructions for setting up the LCM itself.
  7. The configuration is immediately applied by our team.


A simplified diagram of the DSC architecture.

We will begin our acquaintance with the engine by studying the predefined resources. Next, try to write the configuration.

DSC Resources 


DSC resources are a kind of analogue to PowerShell modules. Any Windows server already has a predefined set of DSC resources; they are in the same directory as the PowerShell modules. The list can be obtained through the Get-DscResourse cmdlet. Here's what this list looks like on Windows 10 1809:
 
PS C:\windows\system32> Get-DscResource | Sort-Object -Property Name | ft ImplementedAs, Name -a
 
ImplementedAs Name
------------- ----
   PowerShell Archive
   PowerShell Environment
       Binary File
   PowerShell Group
    Composite GroupSet
       Binary Log
   PowerShell Package
   PowerShell PackageManagement
   PowerShell PackageManagementSource
    Composite ProcessSet
   PowerShell Registry
   PowerShell Script
   PowerShell Service
    Composite ServiceSet
       Binary SignatureValidation
   PowerShell User
   PowerShell WaitForAll
   PowerShell WaitForAny
   PowerShell WaitForSome
   PowerShell WindowsFeature
    Composite WindowsFeatureSet
   PowerShell WindowsOptionalFeature
    Composite WindowsOptionalFeatureSet
   PowerShell WindowsPackageCab
   PowerShell WindowsProcess

The names of the resources give an understanding of what they work with: 

  • Archive - with packing and unpacking archives;
  • Environment - with environment variables;
  • File - with files;
  • Group - with local user groups;
  • Log - with logs;
  • Package —– with software packages;
  • Registry —– with registry keys and their status;
  • Service - with services and their status;
  • User - with local user accounts;
  • WindowsFeature — Windows Server;
  • WindowsProcess — Windows;
  • Script —  PowerShell- . 3 : SetScript — -, TestScript — , , GetScript — .

More often than not, out of the box DSC resources are not enough . In this case, you can write your own scripts for everything that goes beyond the standard module. In order not to reinvent the wheel, you can use DSC resources written by other developers. For example, from here https://github.com/PowerShell/DscResources or from PSGallery
 
How to install additional resources . We use the Install-Module cmdlet . Installing resources is simply copying resource files along one of the paths in the $ env: PSModulePath environment variable . Installed resources are not automatically connected during compilation, so later we will additionally connect them in the configuration itself.

To use a resource, you need to install it locally and on the target server. In On-Premise infrastructures, a security policy typically prohibits Internet access for servers. In this case, the DSC server will not be able to load additional resources from external sources. To publish archives with modules, we deploy a local NuGet repository or a regular web server. You can install additional resources for the web server by unpacking the module into the C: \ Program Files \ WindowsPowerShell \ Modules \ directory.
This is exactly what the Install-Module cmdlet does.

In the second article, we will take a closer look at the difference between the setting for the Push and Pull modes. 

Simple configuration parsing


Configuration is a simple, consistent description of what needs to be done on the server. This is how a simple DSC configuration looks like: 

Configuration EnvironmentVariable_Path
{
param ()
Import-DscResource -ModuleName 'PSDscResources'
Node localhost
    {
        Environment CreatePathEnvironmentVariable
        {
            Name = 'TestPathEnvironmentVariable'
            Value = 'TestValue'
            Ensure = 'Present'
            Path = $true
            Target = @('Process', 'Machine')
        }
    }
}
EnvironmentVariable_Path -OutputPath:"C:\EnvironmentVariable_Path"

For her example, let's see what the configuration consists of.

The Configuration block is a special type of PowerShell function that describes what we want to get. 

Inside the block contains: 

  • param block with parameters that can be used internally; 
  • block with additional PowerShell calls. Here at the beginning of the configuration, we always run Import-Resource to connect additional resources;
  • blocks with settings for specific Node $ servername servers

Inside the Node block, we indicate which resources on a particular server we will configure. In the example above, this is creating an environment variable using a regular Environment resource.

Let's look a little deeper and look at the syntax of a specific resource through the Get-DscResource -Name Environment -Syntax command:
 

PS C:\windows\system32> Get-DscResource -Name Environment -Syntax
Environment [String] #ResourceName
{
    Name = [string]
    [DependsOn = [string[]]]
    [Ensure = [string]{ Absent | Present }]
    [Path = [bool]]
    [PsDscRunAsCredential = [PSCredential]]
    [Value = [string]]
}

In this example: 

  • Name - the name of the environment variable.
  • In DependsOn, we specify the dependency on other resources. It is important to remember that the operation will not be performed until the resource specified here is completed. 
  • In Ensure, we specify the configuration conditions. If such a variable is absent, then we will create it; if it is present, then this resource will not be configured.
  • The Path specifies whether the environment variable contain a path. 
  • In PsDscRunAsCredential specified credentials.
  • Value - the value of the environment variable. 
  • The Target may be indicated in respect of whom the configuration is applied.

How to start compilation . We just call the configuration by its name with the necessary parameters. The result will be a mof file, which is further used by the DSC engine to configure a specific server:
 
PS C:\windows\system32> EnvironmentVariable_Path -OutputPath:"C:\EnvironmentVariable_Path"
 
    Directory: C:\EnvironmentVariable_Path
 
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       25.02.2020     14:05           2172 localhost.mof

Configure Local Configuration Manager


The local configuration manager is responsible for applying the configurations that we compiled into mof files. It is he who monitors the preservation of the state defined in the configuration. If the system leaves this state, LCM calls the code in the resources to restore the specified state. 

Let's see the Local configuration manager settings through Get-DscLocalConfigurationManager:
 
PS C:\windows\system32> Get-DscLocalConfigurationManager
 
ActionAfterReboot              : ContinueConfiguration
AgentId                        : 1FB3A2EE-57C9-11EA-A204-58A023EF3A48
AllowModuleOverWrite           : False
CertificateID                  :
ConfigurationDownloadManagers  : {}
ConfigurationID                :
ConfigurationMode              : ApplyAndMonitor
ConfigurationModeFrequencyMins : 15
Credential                     :
DebugMode                      : {NONE}
DownloadManagerCustomData      :
DownloadManagerName            :
LCMCompatibleVersions          : {1.0, 2.0}
LCMState                       : Idle
LCMStateDetail                 :
LCMVersion                     : 2.0
StatusRetentionTimeInDays      : 10
SignatureValidationPolicy      : NONE
SignatureValidations           : {}
MaximumDownloadSizeMB          : 500
PartialConfigurations          :
RebootNodeIfNeeded             : False
RefreshFrequencyMins           : 30
RefreshMode                    : PUSH
ReportManagers                 : {}
ResourceModuleManagers         : {}
PSComputerName                 :

  • RefreshMode contains the LCM operating mode - Push or Pull. We will talk more about modes in the second article.
  • ConfigurationMode shows the current configuration application mode. In our case, it is ApplyAndMonitor - apply and track changes. ApplyOnly modes are also available (LCM will not track configuration changes) and ApplyAndAutocorrect modes (LCM will not only track changes, but also roll them back to the base configuration).
  • RebootNodeIfNeeded - can reboot the server after the configuration is completed, if necessary to apply the settings. 
  • ConfigurationModeFrequencyMins - Fixes how often LCM will check for configuration changes.

Change LCM settings in meta-configuration. Here is her example:
 

Configuration LCMConfiguration
{
   Node Localhost
   {
       LocalConfigurationManager
       {
           RebootNodeIfNeeeded = $True
       }
   }
}
LCMConfiguration

Same for the latest version of WMF with comments:


 
[DSCLocalConfigurationManager()]
Configuration LCMConfiguration
{
   param
   (
       [string[]]$Server = "localhost"
   )

 

#  LCM:
#  ,  
# : PUSH
#    30 
   Node $Server
   {
       Settings
       {
           RebootNodeIfNeeded = $True
           RefreshMode        = 'Push'
           RefreshFrequencyMins = 30
       }
   }
}


#   
LCMConfiguration -Server "localhost" -OutputPath "C:\DSC\MetaConfigurations\EnvironmentVariable_Path\"

Features metaconfiguration . When writing a metaconfiguration, we use the same blocks as for a regular DSC configuration. The exception is the LocalConfigurationManager (v4) internal block or the DSCLocalConfigurationManager (v5) attribute for a specific server. They describe all the necessary settings. 

The metaconfiguration is also compiled into a mof file, but to use it, the Set-DSCLocalConfigurationManager cmdlet is used, and not Start-DSCConfiguration.
 
PS C:\windows\system32> LCMConfiguration -OutputPath C:\EnvironmentVariable_Path\
 
    Directory: C:\EnvironmentVariable_Path
 
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       26.02.2020     20:05           1016 Localhost.meta.mof

 

PS C:\windows\system32> Set-DscLocalConfigurationManager -Path C:\EnvironmentVariable_Path\
PS C:\windows\system32> Get-DscLocalConfigurationManager
 
RebootNodeIfNeeded             : True

Theoretically, nothing prevents us from combining the configuration of LCM and conventional resources within the same configuration. But for simplicity, it is recommended that the spelling and application of configuration and metaconfiguration be separated.

Partial Configurations 


Partial Configurations - several configurations that are executed sequentially one after another. They are useful when several teams work on the service. Each of the commands indicates the settings for its part of the service, and the partial configuration then applies all the settings sequentially. In LCM settings, we specify partial configurations in PartialConfigurations

In partial configurations, we must consider the straightforward DSC logic. The engine does not allow branching, so additional resources are needed to fulfill various conditions. We will analyze this with a few examples. 

Suppose we want to guaranteedly deliver the configuration to the server and proceed according to the following algorithm:

  1. First, check that the necessary modules are installed on the server.
  2. We perform the configuration, which will bring the server to the desired state.

Here's what a metaconfiguration looks like with several consecutive configurations inside:


#   
[DSCLocalConfigurationManager()]
configuration MetaPushConfig
{
param
   (
       [ValidateNotNullOrEmpty()]
       [string] $NodeName = 'localhost'
   )

 

   Node $NodeName
   {
 	
 #      LCM,    
       PartialConfiguration ModulesDownloadConfig
       {
               Description = 'Download and install modules'
               RefreshMode = 'Push'
       }

 

       #        
       PartialConfiguration ServerOSConfig
       {
               DependsOn = "[PartialConfiguration]ModulesDownloadConfig"
               Description = 'Configuration'
               RefreshMode = 'Push'
       }

 

       #   LCM
       Settings
       {
               RefreshMode        = 'Push'
               RefreshFrequencyMins = 30
               RebootNodeIfNeeded = $true
       }
   }
}

 
 

#  
MetaPushConfig -NodeName "NewServer.contoso.com" -OutputPath c:\DSC\MetaConfigurations

 

#      
$cred = (Get-Credential -UserName Administrator -Message "Enter admin credentials")

 

#  LCM   
Set-DscLocalConfigurationManager -ComputerName "NewServer.contoso.com" -Credential $cred -Path "c:\DSC\MetaConfigurations" -Verbose -Force

 

#  
Publish-DscConfiguration c:\DSC\Configurations\ModulesDownloadConfig -ComputerName "NewServer.contoso.com" -Credential $cred -Force
Publish-DscConfiguration c:\DSC\Configurations\ServerOSConfig -ComputerName "NewServer.contoso.com" -Credential $cred -Force

 

#    
Start-DscConfiguration -UseExisting -ComputerName "NewServer.contoso.com" -Credential $cred -Force -Verbose

The first part of the configuration says Download and install module s: download and install the necessary resources. The second part of ServerOSConfig brings the server to the desired state. What are the nuances with DSC straightforwardness here: 

  1. If the first part of the configuration initially returned FALSE and completed completely, then LCM will not go to the second configuration. According to the DSC logic, first you need to bring the server to the first state described. 
    How to treat: run the configuration twice or automate the entire process in a script.
  2. If the server after installing any component requires a reboot, then the configuration will not go further until we ourselves reboot the server. Even if we tell LCM that RebootNodeIfNeeeded = $ True, the agent will wait for our solution during configuration.
    How to treat: the xPendingReboot resource comes to the rescue , which controls the registry key upon reboot. This resource restarts the server for us.

Here is an example configuration for downloading and installing resources in the “bloody enterprise” scenario, when the server does not have Internet access. It assumes the presence of a web server, where pre-downloaded resources are available to everyone via the http protocol.


Configuration ModulesDownloadConfig
{
   param
   (
       [string[]]$Server
   )

 

   #   
   Import-DscResource -ModuleName "PSDesiredStateConfiguration"

 

   #  
   Node $Server
   {
       #  IE Security
       Registry DisableIEESC-Admin {
           Key = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}"
           ValueName = "IsInstalled"
           Ensure = "Present"
           ValueData = 0
           ValueType = "DWORD"
       }

 

       Registry DisableIEESC-User {
           Key = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}"
           ValueName = "IsInstalled"
           Ensure = "Present"
           ValueData = "0"
           ValueType = "DWORD"
       }

 

	
 #    ,    
       File CreateDistribDir {
           Ensure          = "present"
           DestinationPath = "C:\Install\PSModules"
           Type            = "Directory"
       }

 

	
 #   NetworkingDsc (<a href="https://www.powershellgallery.com/packages/NetworkingDsc/8.0.0-preview0004">https://www.powershellgallery.com/packages/NetworkingDsc/8.0.0-preview0004</a>),      
       Script NetworkingDscDownLoad {
           SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/NetworkingDsc.zip" -OutFile "C:\Install\PSModules\NetworkingDsc.zip" }
           GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\NetworkingDsc.zip"
               GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
               }
           }
           TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\NetworkingDsc" }
       }

 

 #   NetworkingDsc  C:\Program Files\WindowsPowerShell\Modules
       Archive UnpackNetworkingDsc {
           Ensure = "Present"
           DependsOn = "[Script]NetworkingDscDownLoad"
           Path = "C:\Install\PSModules\NetworkingDsc.zip"
           Destination = "C:\Program Files\WindowsPowerShell\Modules\"
       }

 

 #   ComputerManagementDsc (<a href="https://www.powershellgallery.com/packages/ComputerManagementDsc/8.2.1-preview0001">https://www.powershellgallery.com/packages/ComputerManagementDsc/8.2.1-preview0001</a>),      
       Script ComputerManagementDscDownLoad {
           SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/ComputerManagementDsc.zip" -OutFile "C:\Install\PSModules\ComputerManagementDsc.zip" }
           GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\ComputerManagementDsc.zip"
               GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
               }
           }
           TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\ComputerManagementDsc" }
       }

 

	
 #   ComputerManagementDsc  C:\Program Files\WindowsPowerShell\Modules
       Archive UnpackComputerManagementDsc {
           Ensure = "Present"
           DependsOn = "[Script]ComputerManagementDscDownLoad"
           Path = "C:\Install\PSModules\ComputerManagementDsc.zip"
           Destination = "C:\Program Files\WindowsPowerShell\Modules\"
       }

 

	
 #   xPendingReboot (<a href="https://www.powershellgallery.com/packages/xPendingReboot/0.4.0.0">https://www.powershellgallery.com/packages/xPendingReboot/0.4.0.0</a>),      
       Script xPendingRebootDownLoad {
           SetScript = { Invoke-WebRequest -Uri "http://repo.contoso.com/repo/modules/xPendingReboot.zip" -OutFile "C:\Install\PSModules\xPendingReboot.zip" }
           GetScript = { return @{ Result = Test-Path "C:\Install\PSModules\xPendingReboot.zip"
               GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
               }
           }
           TestScript = { Test-Path "C:\Program Files\WindowsPowerShell\Modules\xPendingReboot" }
       }

 

	
 #   xPendingReboot  C:\Program Files\WindowsPowerShell\Modules
       Archive UnpackxPendingReboot {
           Ensure = "Present"
           DependsOn = "[Script]xPendingRebootDownLoad"
           Path = "C:\Install\PSModules\xPendingReboot.zip"
           Destination = "C:\Program Files\WindowsPowerShell\Modules\"
       }
   }
}

About security


The fat minus of DSC on the ground is the lack of Run As Accounts. This mechanism safely stores accounts in the form of username + "salt hash of the password" and palm off them to the service, which is responsible for authentication. Without it, DSC cannot manage accounts on behalf of another service. And if we need to authenticate under an account with special privileges, the automation process is greatly complicated. For example, this will be the case when entering the server into the domain.

Everything at our disposal:

  • credentials are stored in Plain Text,
  • encrypted credentials only work on the PC where they were generated.

In practice, there are several solutions. 


"Stubs" are good when predictability of server behavior must be ensured.
We do a “stub” after the server configuration in several cases, if we used:

  • reboot (xPendingReboot resource) 
  • credential transfer 
  • other resources that may affect the server’s performance regarding unplanned reboots or security.

To do this, create and republish configurations WITHOUT blocks containing the xPendingReboot resource and configurations with credentials.

This approach applies only to the Push scenario, since Pull is fairly straightforward and does not imply a configuration change on the fly. In the next article frombuldakovWe will take a closer look at the settings and features of working in the Push and Pull modes.

And also come to discuss the nuances of DSC technology and the boundaries of its application on May 28 at 18.00 at the first online meeting of the Raiffeisenbank DGTL Communications community. Also at the meeting we’ll talk about how to make ELK and Exchange friends and what Microsoft Endpoint Manager can do in device management. Here is the registration for the mitap .

All Articles