Practical use Desired State Configuration for Windows Server 2012 R2



Linux Administrators: this article is about “Puppet” for Windows, and already have a beta version of DSC for Linux.
For those who are in the subject: nothing about new PowerShell 5.0, only about what is available out of the box Windows Server 2012 R2.


the

preamble


In 2013 with the release of Windows Server 2012 R2, Microsoft announced the advent of Powershell Desired State Configuration (DSC).

At this point, I more or less imagined doing a similar system for Linux (for example, the already mentioned Puppet). Therefore, this opportunity seemed to me insufficient for a full automatic configuration of the system. And only the recent messages about the upcoming Powershell 5.0 and what's new in DSC prompted me again to pay attention to this technology.

To understand, I made the problem simpler:

the
    the
  • Suppose, there is a client who wants to set their own we have developed ASP.NET the application on your server. In addition to IIS, we need MS SQL Server, and also need to do some configuration of the operating system and install some important tools.

If instead the installation instructions and setup to give out a configuration script that will do all that is required on a newly installed Windows Server 2012 R2?

To better understand this article, perhaps you should read the description in the Microsoft blog post — http://habrahabr.ru/company/microsoft/blog/253497/.

the

Initial position


Initially it was assumed that somewhere at the hosting provider you have ordered the server. It is installed with Windows Server 2012 R2 and we just got an alert with the administrator password and ip address of the server.

And we are entering one command, such as:

the
makemagic -server new.example.com

and after some time received ready-to-use system.

Unfortunately, it's impossible. But I have good news — it will be in the next version of Windows Server 2016. As I write this article, the following configuration (without installing updates) is rolled on only installed Technical Preview 3.

Server


If you have a Windows Server 2012 R2 with integrated updates, which you can use — feel free to skip this section.

In the current version (2012 R2) is a problem in the chain of updates:

    One of the first tasks I have tried to check/adjust the time zone. For this you need to install the latest time zone update. the

  1. This update does not install, as it requires a big update KB2919355.
  2. the
  3. Which in turn wants to update KB2975061 is in my case.

None of these updates are not available for installation through Windows Update on a newly installed system.

So there are two options: 1) install all updates through Windows Update, but it will be long (this process could be done later), or 2) to put only a few of the most necessary.

In the second case, do this: connect via RDP to the server, launch a Powershell console with elevated privileges and downloaded the needed updates on the direct link and install them:

the
Invoke-WebRequest -Uri http://download.microsoft.com/download/3/9/7/3971FEA1-C483-409E-BF13-219F8A6E907E/Windows8.1-KB2975061-x64.msu -OutFile .\Downloads\Windows8.1-KB2975061-x64.msu
.\Downloads\Windows8.1-KB2975061-x64.msu /quiet /norestart
Invoke-WebRequest -Uri http://download.microsoft.com/download/2/5/6/256CCCFB-5341-4A8D-A277-8A81B21A1E35/Windows8.1-KB2919355-x64.msu -OutFile .\Downloads\Windows8.1-KB2919355-x64.msu
.\Downloads\Windows8.1-KB2919355-x64.msu /quiet /promtrestart

After restarting our server is ready to receive configuration.

Once again I remind that Windows Server 2016 TP3 is immediately ready to experiment with DSC.

Computer administrator


Important: So some of you might have missed the previous section: To test this example configuration in any way connect to the server the way MS SQL Server 2014, I chose Express Edition and connect to R:.


Create and apply configuration was performed on Windows 8.1. Version Powershell:

the
PS C:\Users\nelsh> $PSVersionTable

Name Value 
---- ----- 
PSVersion 4.0 
WSManStackVersion 3.0 
SerializationVersion 1.1.0.1 
CLRVersion 4.0.30319.34209 
BuildVersion 6.3.9600.17400 
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0} 
PSRemotingProtocolVersion 2.2 

But that is not enough. The computer administrator must enable Powershell Remoting (server, after installing the two updates that PSRemoting is already enabled). This is done in a Powershell console with elevated privileges:

the
Enable-PSRemoting -Force

In addition, you need to resolve (!) from the administrator's PC to connect to other computers.

the
Set-Item WSMan:\localhost\Client\TrustedHosts -Value *

For convenience, I also added in the file hosts is a c string with the ip address of the created server.

the
<ip address> cs1.example.com

Source code for the example is available on Github: https://github.com/nelsh/DSC-WS2012R2.

the

configuration


This is a screenshot of the first version of file DSC-W2012R2.ps1 script (in the repository, for convenience, it is under the name DSC-W2012R2-First.ps1).



the
    the
  • 1-21-line — actually the configuration named DSCW2012R2.
    • the
    • In it for 3-7 line we announce that there will be only one parameter — an array of server names.
    • the
    • In line 8 we connect the required PowerShell module
    • the
    • 10-20 lines — a list of resources used in the configuration. In our case, only one resource Script, read about it below.
    the
  • 23-25 lines — as one of the steps is to add a user, we will need to create a password. To not deal with encryption — allow you to store passwords in configuration in cleartext.
  • the
  • 27 line — create configuration. The result of running this script we have a file DSCW2012R2\cs1.example.com.mof something like a compiled configuration.
  • the
  • Starting with 33 lines — example for simultaneous adjustment of a pair of servers.

Back to resources — Powershell DSC has 12 built-in resources. Most (or rather 11 of 12) of them simple and straightforward. But for full customization of the system is clearly not enough. Microsoft offers to create the necessary resources. But honestly, even now I have no particular desire to deal with it.

However, when we first met I did not pay attention to the online Script. Consider the example read more:

the
 Script First {
TestScript = { if ( "Test script content" ) { $true } else { $false } }
SetScript = { "Set script content" }
GetScript = { return @{ Result = "Result for GetScript"
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}

This is the easiest option that does nothing. How it works:

the
    the
  • To apply the configuration we call Start-DSCConfiguration (line 29).
  • the
  • When processing a resource list, the script comes to resource “Script First”, which first calls code from a variable TestScript. In our example, it always returns $true.
  • the
  • But if it returned $false, then volunteered would SetScript code from the variable.

These variables can be any code to Powershell — the scale only depend on our imaginations. Ideally, the code in TestScipt should check that all settings that are performed in the code SetScript.

Let's try to run our first configuration (an old habit I run from the Far Manager):
the
powershell.exe -ExecutionPolicy RemoteSigned .\DSC-WS2012R2.ps1



Completed without errors.

Not to explain how to run from the administrator's PC, I went to our server and run a few commands.



First: Test-DSCConfiguration tests the configuration. Please note on last message (after the yellow text) is True. Ie configuration is checked and no error detected.

The following command: Get-DSCConfiguration — reports details about the current configuration. It is sufficient to test the code of our online “Script First” with this screenshot to understand what and where taken.

In that moment I realized that this might actually work.

So. Moving on to...

the

Advanced configuration


Start adding real tasks to your configuration nodes.

The first thing I would like to be sure that the computer name and primary dns suffix. If our DNS zone this machine will be called cs1.example.com the name is cs1, and the dns suffix is example.com

Let's start with the name. At first, I wrote this code:

the
 $shortName = $Server.Split(".")[0].ToLower()
Script ComputerName {
SetScript = { Rename-Computer-NewName $shortName }
GetScript = { return @{ Result = $env:computerName
GetScript = $GetScript.Trim(); SetScript = $SetScript.Trim(); TestScript = $TestScript.Trim() } }
TestScript = { $env:computerName.ToLower() -eq $shortName }
}

But it's not working. SetScript, GetScript and TestScript don't know anything about variables outside their zone of visibility. You can pass only using string formatting. Here it is:

the
 $shortName = $Server.Split(".")[0].ToLower()
Script ComputerName {
SetScript = ({
Rename-Computer-NewName "{0}"
} -f @($shortName))
GetScript = { return @{ Result = $env:computerName
GetScript = $GetScript.Trim(); SetScript = $SetScript.Trim(); TestScript = $TestScript.Trim()
}
}
TestScript = ({ 
$env:computerName.ToLower() -eq "{0}" 
} -f @($shortName))
}

Checking the dns suffix much easier is a setting in the registry, so we use the default resource Registry:

the
 Registry PrimaryDomainSuffix {
Key = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\"
ValueName = "NV Domain"
Ensure = "Present"
ValueData = "example.com"
ValueType = "String"
}

During the experiments, the updates were not automatically set up Windows Update. Only get notifications about available updates:

the
 Script WindowsUpdateSettings {
SetScript = {
$WUSettings = (New-Object -com "Microsoft.Update.AutoUpdate").Settings
$WUSettings.NotificationLevel=2
$WUSettings.IncludeRecommendedUpdates=$true
$WUSettings.Save()
}
GetScript = { return @{ Result = "
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { 
$WUSettings = (New-Object -com "Microsoft.Update.AutoUpdate").Settings; $WUSettings.NotificationLevel -eq 2-and $WUSettings.IncludeRecommendedUpdates -eq $true
}
}

Get a few important updates and still custom time zone


The next important parameter is the time zone. And why I only suggested that Moscow time is not for everyone? Apparently, for this:

So, the first problem: it seems that built-in tools install individual updates via Windows Update does not exist. Fortunately, there is a small utility for this task.

Create a directory for the utility:

the
 $abcUpdatePath = "C:\UTILS\ABC-Update"
$abcUpdateZip = Join-Path $abcUpdatePath "ABC-Update.zip"
File AbcUpdateDir {
Ensure = "present"
DestinationPath = $abcUpdatePath
Type = "Directory"
}

Download:

the
 Script AbcUpdateDownload {
DependsOn = "[File]AbcUpdateDir"
SetScript = ({
Invoke-WebRequest -Uri http://abc-deploy.com/Files/ABC-Update.zip -OutFile {0}
} -f @($abcUpdateZip)) 
GetScript = { 
return @{ Result = $TestScript
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = ({
Test-Path {0} 
} -f @($abcUpdateZip)) 
}

Unpack using the built-in online Archive:

the
 Archive AbcUpdateUnpack {
Ensure = "Present"
DependsOn = "[Script]AbcUpdateDownload"
Path = $abcUpdateZip
Destination = $abcUpdatePath
}

Run, and, except for the time zone update, will update .NET Framework to version 4.5.2:
the
 Script AbcUpdateNet452Install {
DependsOn = "[Archive]AbcUpdateUnpack"
SetScript = { C:\UTILS\ABC-Update\ABC-Update.exe /a:install /k:2934520 }
GetScript = { return @{ Result = if ( Get-HotFix-Id KB2934520 -ErrorAction SilentlyContinue ) { "KB2934520: Installed" } else { "KB2934520: Not Found" }
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { if ( Get-HotFix-Id KB2934520 -ErrorAction SilentlyContinue ) { $true } else { $false } }
}

Script AbcUpdateTimeZoneInstall {
DependsOn = "[Archive]AbcUpdateUnpack"
SetScript = { C:\UTILS\ABC-Update\ABC-Update.exe /a:install /k:3013410 }
GetScript = { return @{ Result = if ( Get-HotFix-Id KB3013410 -ErrorAction SilentlyContinue ) { "KB3013410: Installed" } else { "KB3013410: Not Found" }
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { if ( Get-HotFix-Id KB3013410 -ErrorAction SilentlyContinue ) { $true } else { $false } }
}

Finally we got to the time zones. A method scientific I chose a time zone near lake Baikal.

I found this option: to change the time zone only by using command-line utilities tzutil.exe and to check only by using Powershell. But this case is special — when you install a used one value "North Asia East Standard Time", and checked absolutely another "Russia TZ 7 Standard Time":

the
 Script TimeZoneSettings {
SetScript = { tzutil.exe "North Asia East Standard Time" }
GetScript = { return @{ Result = [System.TimeZone]::CurrentTimeZone.StandardName
GetScript = $GetScript.Trim(); SetScript = $SetScript.Trim(); TestScript = $TestScript.Trim()
}
}
TestScript = { [System.TimeZone]::CurrentTimeZone.StandardName -eq "Russia TZ 7 Standard Time" }
}

It seems that the trouble with all time zones of Russia.

Windows Components


With them it's very simple and a very large number of examples in the Internet. It may seem that Windows administrators deal only with the installation and removal of components. Only the first two WindowsFeature resource configuration:

the
 WindowsFeature offFSSMB1 {
Ensure = "Absent"
Name = "FS-both smb1"
}
WindowsFeature WebAspNet45 {
Ensure = "Present"
Name = "Web-Asp-Net45"
IncludeAllSubFeature = $True
}

In the first case the component is removed, the second put together with all the dependencies.

package Installation


For example, Far Manager. First, the package you need to download the already familiar way:

the
 Script FarDownLoad {
SetScript = { Invoke-WebRequest -Uri http://www.farmanager.com/files/Far30b4400.x64.20150709.msi -OutFile C:\Users\Public\Downloads\Far30b4400.x64.20150709.msi }
GetScript = { return @{ Result = Test-Path C:\Users\Public\Downloads\Far30b4400.x64.20150709.msi
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { Test-Path C:\Users\Public\Downloads\Far30b4400.x64.20150709.msi }
}

In the resource settings Package is "ProductId". And it seems there is even a program that analyses the. msi file and reports this same "ProductId". I went ahead as soon as I tried to apply the configuration without this parameter, and the error text found "ProductId" and the correct "Name". A description of the resource were as follows:

the
 Package FarInstall {
Ensure = "Present"
DependsOn = "[Script]FarDownLoad"
Name = "Far Manager 3 x64"
ProductId = 'E5512F32-B7C1-48E3-B6AF-E5F962F99ED6'
Path = "C:\Users\Public\Downloads\Far30b4400.x64.20150709.msi"
Arguments = "
LogPath = "C:\Users\Public\Downloads\FarInstall.log"
}

Users and rights


The formulation of the problem, the server is under the control of the customer, but nevertheless, I admitted that there will be the ability to update web application with our continuous integration server. We use Jenkins CI (by the way, all the problems is also implemented in Powershell).

In the minimum version we need a Jenkins user in the Users group with write access to the directory hosting the web application. Let it be c:\web.

The user is created this way:

the
 $JenkinsCredential = New-Object System.Management.Automation.PSCredential(`
"Jenkins", ("Pa`$`$w0rd" | ConvertTo-SecureString -asPlainText-Force)`
)
User JenkinsUser {
UserName = "Jenkins"
Ensure = "Present"
Password = $JenkinsCredential
PasswordChangeNotAllowed = $true
PasswordNeverExpires = $true
}

Is there a way to use in the configuration of the encrypted passwords, but we come in a simple way. In this case, you will create the user "Jenkins" with password "Pa$$w0rd".

Directory creation is already a familiar way. But with permissions on directories and check had been tinkering:
the
 $AccessStringTmpl = "NT AUTHORITY\SYSTEM Allow FullControl'nBUILTIN\Administrators Allow FullControl'nBUILTIN\Users Allow ReadAndExecute, Synchronize'nCS1\Jenkins Allow Modify, Synchronize"
File DirDweb {
Ensure = "present"
DestinationPath = "c:\web"
Type = "Directory"
}
Script AclsDweb
{
DependsOn = "[File]DirDweb"
SetScript = {
icacls c:\web /reset /t /q
takeown.exe /f c:\web /r /a /d y
icacls.exe c:\web the /inheritance:r
icacls.exe c:\web /grant:r "Administrators:(OI)(CI)(F)" "System:(OI)(CI)(F)" "Users:(OI)(CI)(RX)" "Jenkins:(OI)(CI)(M)" /t /q
}
GetScript = { return @{ Result = (get-acl c:\web).AccessToString
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = ({ (get-acl c:\web).AccessToString -eq "{0}" 
} -f @($AccessStringTmpl))
}

Easier to assign rights using the icacls.exe. In this case, is executed in order:

    the
  1. in the first line: a reset of all rights and enable inheritance from a parent directory
  2. second: the owner is assigned to the built-in Administrators group the

  3. in the third: the inheritance and removed all rights
  4. the
  5. in the fourth: assign full rights to Administrators and SYSTEM, read for users and a change to Jenkins.

To check use the method (get-acl c:\web).AccessToString — the resulting string must match the variable $AccessStringTmpl. By the way, the example error — string explicitly specified the server name is “CS1” and has substituted the value of $Server.Split(".")[0].ToUpper().

MS SQL


I somewhat regretted that decided not to use third-party modules. As there is already a module for installation and configuration of MS SQL Server. But I have a configuration file for unattended installation and I decided to try it.

First, we'll need one more component of Windows resource “WindowsFeature NetFrameworkCore”:

the
 WindowsFeature NetFrameworkCore {
Ensure = "Present"
Name = "Net-Framework-Core"
IncludeAllSubFeature = $True
}

Secondly, the configuration file for the installer — resource “Script MSSQLConfigDownLoad”:

the
 Script MSSQLConfigDownLoad {
SetScript = { Invoke-WebRequest -Uri https://raw.githubusercontent.com/nelsh/DSC-WS2012R2/master/SQL2014-Setup.ini -OutFile C:\Users\Public\Downloads\SQL2014-Setup.ini }
GetScript = { return @{ Result = Test-Path C:\Users\Public\Downloads\SQL2014-Setup.ini
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { Test-Path C:\Users\Public\Downloads\SQL2014-Setup.ini }
}

Third, you forgot to mount the image with the distribution of any edition of MS SQL Server 2014?

the
 Script MSSQL {
SetScript = { r:\setup.exe /configurationfile=C:\Users\Public\Downloads\SQL2014-Setup.ini /SAPWD=1q@w3e }
GetScript = { return @{ Result = if ( Get-Service-Name "MSSQLSERVER" -ErrorAction SilentlyContinue ) { "MSSQLSERVER Servise is exist" } else { "MSSQLSERVER Servise not found" }
GetScript = $GetScript; SetScript = $SetScript; TestScript = $TestScript
}
}
TestScript = { if ( Get-Service-Name "MSSQLSERVER" -ErrorAction SilentlyContinue ) { $true } else { $false } }
}

I used the disk with the Express Edition, which are connected as R:. In the process you will set the Database Engine plus FullSearch and administration tools. Check TestScript in the most simple — is the service MSSQLSERVER or not.

... And when the processes list on the server I realized that the installation started and it became clear that the experiment can be
considered completed.

Further configuration may depend on the situation: if you need urgently, urgently to show a working application, then somehow get and install our app.

If we have a scheduled installation (without a rush), then you can pre-configure the firewall and to install utilities (monitoring, backup) — all can be your options. That is, the greatest difficulty may cause only AWStats code in TestScript will resemble a small program, but it also can be solved.

So, at this point I decided to stop. In my opinion, is a good example that anyone can adapt to their situation.

the

Preliminary results


In my opinion, DSC is possible to adopt without waiting for the next version of Windows Server.

In the domain infrastructure to completely replace the group policy, this technology can not, but it has certain advantages:
the
    the
  • Can be used in both manual mode and auto configuration server.
  • the
  • Can be used regardless of the Active Directory, maybe together with the group's policies.
  • the
  • the directory with the configurations, you can put in a version control system.
  • the
  • With the release of Powershell 5.0, we obtain an easy way to use additional modules, see powershellgallery.com. There are already several dozen modules created by the community. Perhaps some of them already have someone that can replace the scripts of my example.

Please note that in the example in this article, errors are possible and probably have more effective solutions.

important note: as I understand it, the issue needed to restart the configuration process even in the new versions has no solution. Therefore, the above-described configuration is applied in two passes — both 2012R2 and 2016 — breaks on the installation of components and asks for a reboot. Then you must rerun the application configuration.

Useful links


the
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Performance comparison of hierarchical models, Django and PostgreSQL

Transport Tycoon Deluxe / Emscripten part 2

google life search