This is post 2 of 7 in a series titled Development environments with PowerShell DSC
- Development environments with PowerShell DSC – Introduction to DSC
- Development environments with PowerShell DSC – Windows Features
- Development environments with PowerShell DSC – Mongo DB
- Development environments with PowerShell DSC – SQL Server
- Development environments with PowerShell DSC – Sitecore
- Development environments with PowerShell DSC – Coveo CES
- Development environments with PowerShell DSC – Coveo REST API & Coveo for Sitecore
I kicked off this series last week with a look at what PowerShell DSC is. This week I'm going to look at scripts you can use for basic configuration of Windows itself for a hypothetical development environment. I'll cover basic Windows features, a few issues you might encounter and helpful tweaks like being able to disable IE's "enhanced security" mode if you're working on a server.
Before I get going, couple of notes about the examples I'm including in this series of posts: They all have a call to `Start-DSCConfiguration` using the "-Force" flag to make DSC run this configuration immediately in "push" mode. However, the `Configuration` blocks declared should all work in pull configurations. Writing the scripts this way just makes them easier for people to try out without any other setup effort. Also the examples are all pretty much self-contained, so the overall configuration of a server using them would rely on many separate scripts. You don't necessarily need to have as many as I'm showing here. It's more to keep the examples clear and usable than to represent the "right" architecture. You're free to merge things together, or even break them apart further if that suits you.
Whichever way you choose to go, the rest of my write-up assumes you have a base install of Windows Server 2012. Many of these techniques will work on other versions of Windows (client and server) – you just need to ensure that your base install includes PowerShell 4 and WMI 4.
So in order to sort this out for things like IIS and .Net frameworks, we need to have a core setup script. As we saw with last week's post, asking DSC to install a Windows Feature for us is pretty easy with the
WindowsFeature
resource. You just need to know Windows' internal code name for the feature you want to add. So how do we work those out?
The answer is
the
Get-WindowsFeature
CommandLet in PowerShell. This returns a helpful list of all the features available, along with their current state and their internal name. After running that you might see:
Armed with that data, you can go through the requirements you have for features your dev environment will need, and sort out a list to transform into a set of DSC resource requests. Most of these are simple. You just end up with a pretty long script, as you often have to install quite a few features and sub-features.
Depending on what your install critera are, you may be able to
simplify this with the
IncludeAllSubFeature
property
of the
WindowsFeature
resource. This allows you to specify the top-level feature and have DSC install every one of its child features. However if you explicitly need to install just some features, you do seem to need to list them out one by one.
You might end up with something like:
Configuration WindowsFeatureInstall { Node $AllNodes.where{ $_.Role.Contains("WindowsFeatures") }.NodeName { WindowsFeature WebServer { Name = "Web-Server" Ensure = "Present" } WindowsFeature WebWebServer { Name = "Web-WebServer" Ensure = "Present" } WindowsFeature WebCommonHttp { Name = "Web-Common-Http" Ensure = "Present" } WindowsFeature WebDefaultDoc { Name = "Web-Default-Doc" Ensure = "Present" } WindowsFeature WebDirBrowsing { Name = "Web-Dir-Browsing" Ensure = "Present" } WindowsFeature WebHttpErrors { Name = "Web-Http-Errors" Ensure = "Present" } WindowsFeature WebStaticContent { Name = "Web-Static-Content" Ensure = "Present" } WindowsFeature WebHttpRedirect { Name = "Web-Http-Redirect" Ensure = "Present" } WindowsFeature WebDavPublishing { Name = "Web-DAV-Publishing" Ensure = "Present" } WindowsFeature WebHttpLogging { Name = "Web-Http-Logging" Ensure = "Present" } WindowsFeature WebLogLibraries { Name = "Web-Log-Libraries" Ensure = "Present" } WindowsFeature WebRequestMonitor { Name = "Web-Request-Monitor" Ensure = "Present" } WindowsFeature WebHttpTracing { Name = "Web-Http-Tracing" Ensure = "Present" } WindowsFeature WebPerformance { Name = "Web-Performance" Ensure = "Present" } WindowsFeature WebStaticCompression { Name = "Web-Stat-Compression" Ensure = "Present" } WindowsFeature WebDynamicCompression { Name = "Web-Dyn-Compression" Ensure = "Present" } WindowsFeature WebSecurity { Name = "Web-Security" Ensure = "Present" } WindowsFeature WebFiltering { Name = "Web-Filtering" Ensure = "Present" } WindowsFeature WebBasicAuth { Name = "Web-Basic-Auth" Ensure = "Present" } WindowsFeature WebCertProvider { Name = "Web-CertProvider" Ensure = "Present" } WindowsFeature WebClientAuth { Name = "Web-Client-Auth" Ensure = "Present" } WindowsFeature WebDigestAuth { Name = "Web-Digest-Auth" Ensure = "Present" } WindowsFeature WebUrlAuth { Name = "Web-Url-Auth" Ensure = "Present" } WindowsFeature WebWindowsAuth { Name = "Web-Windows-Auth" Ensure = "Present" } WindowsFeature WebAppDev { Name = "Web-App-Dev" Ensure = "Present" } WindowsFeature WebNetExt { Name = "Web-Net-Ext" Ensure = "Present" } WindowsFeature WebNetExt45 { Name = "Web-Net-Ext45" Ensure = "Present" } WindowsFeature WebAspNet { Name = "Web-Asp-Net" Ensure = "Present" } WindowsFeature WebAspNet45 { Name = "Web-Asp-Net45" Ensure = "Present" } WindowsFeature WebIsapiExt { Name = "Web-ISAPI-Ext" Ensure = "Present" } WindowsFeature WebIsapiFilter { Name = "Web-ISAPI-Filter" Ensure = "Present" } WindowsFeature WebMgmtConsole { Name = "Web-Mgmt-Console" Ensure = "Present" } WindowsFeature NetFrameworkFeatures { Name = "NET-Framework-Features" Ensure = "Present" } WindowsFeature NETFrameworkCore { Name = "NET-Framework-Core" Ensure = "Present" } WindowsFeature NETFramework45Features { Name = "NET-Framework-45-Features" Ensure = "Present" } WindowsFeature NETFramework45Core { Name = "NET-Framework-45-Core" Ensure = "Present" } WindowsFeature NETFramework45ASPNET { Name = "NET-Framework-45-ASPNET" Ensure = "Present" } } } WindowsFeatureInstall -ConfigurationData "configData.psd1" Start-DscConfiguration -Path .\WindowsFeatureInstall -Verbose -Wait -Force
This (big) example is ensuring you have key IIS features and the ASP.Net v4 runtime features ready for running websites.
It's making reference to a config data file. I talked a bit about this last week, but for the purposes of the examples in this post, all it needs to define is the server / role data I talked about before. For example:
@{ AllNodes = @( @{ NodeName = "WIN-AQEKG7L9SE8" Role = "Setup, DesktopExperience, WindowsFeatures, IE, SqlServer, MongoDB, Sitecore" } ); }
Future posts will extend this with more data. But for the moment, that can be saved as
configData.psd1
and the examples here will consume it.
In the short time I've spent with DSC so far, I have not managed to work out a "nice" DSC-based approach to handling this reboot work. There are a variety of tricks you can use, mostly based around breaking out these reboot-requiring features to a separate script. It only requires one feature, so it's a trivial script:
Configuration DesktopExperienceInstall { Node $AllNodes.where{ $_.Role.Contains("DesktopExperience") }.NodeName { WindowsFeature WebDavDesktopExperience { Name = "Desktop-Experience" Ensure="Present" } } } DesktopExperienceInstall -ConfigurationData "configData.psd1" Start-DscConfiguration -Path .\DesktopExperienceInstall -Verbose -Wait -Force
You can execute this, trigger a reboot (either manually or via the
Restart-Computer
CommandLet) and then run your other scripts to finish the process. A colleague of mine has put together some interesting script that makes use of workflows to enable this without user-interaction. However I plan to come back to this issue to discover the "proper DSC" approach when I've had some more time to work on it.
The default install of Windows Server has Internet Explorer configured in a strongly locked-down state. (Referred to as IE's "Enhanced Security Configuration") This makes sense if the machine is going to run as a proper server, as it helps to reduce the surface area for attacks. But it's a bit of a pain if you're using a server for development tasks, as it makes IE very difficult to use to test your websites. Hence turning this feature off can be helpful for setting up a development machine.
Now, out of the box DSC doesn't have a resource for making this config change. But an extension exists to enable it. So this is a good example for showing how you can add 3rd party resources into your DSC-based processes. These extensions are just PowerShell Modules that implement a DSC Resource, so they can be installed simply via DSC. For example, the extension that contains the resource for disabling IE's security config is called
xSystemSecurity
. Generally DSC extensions prefixed with "x" are "experimental" bits of code that Microsoft are considering adding into the core of DSC in future versions. The module
can be downloaded from the Technet Gallery.
So to make this configuration change we need to do two things on our target server. First we need to install the
xSystemSecurity
module and then we need to make use of it.
This is the first point where we've come across a DSC script having a dependency on an extrnal file, so it's a good point to introduce an approach to dealing with these. Generally, you'll want to have a central location for any files your scripts depend on. That way we can pass in this location, and have the scripts download their dependencies onto whatever machine they're executing on. Achieving this needs us to make a one important change to how we're working.
A DSC
Configuration
can use exactly the same syntax for passing in command-line parameters as an ordinary PowerShell script function. So we can declare a
-PackagePath
parameter. This can then be used to locate the
xSystemSecurity
package (which happens to be supplied as a zip file) and uncompress it to the right location.
A DSC script to achieve this might look like:
Configuration SetupInstall { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [String] $PackagePath ) Node $AllNodes.where{ $_.Role.Contains("Setup") }.NodeName { Archive AddxSystemSecurity { Path = "$PackagePath\xSystemSecurity_1.0.zip" Destination = "$Env:ProgramFiles\WindowsPowerShell\Modules" } } } SetupInstall -ConfigurationData "configData.psd1" -PackagePath "\\VBoxSvr\dsc\Setup" Start-DscConfiguration -Path .\SetupInstall -Verbose -Wait -Force
The
param()
declares a required paramater of type string called
$PackagePath
which we can refer to elsewhere in our script. The
Archive
resource type knows how to unzip an archive file for us. We specify the source path using our
$PackagePath
variable, and the target path using the special PowerShell syntax for accessing Environment Variables. This allows us to find the machines's "Program Files" folder without worrying about it's actual path.
Note that the call to invoke
SetupInstall
now takes this extra parameter specifying where our central repository of files is. That means the parameter's data is available when the
.MOF
file is being generated by DSC.
It's worth noting that you have to be a bit careful about disk permissions with your shares here. The underlying engine which runs the DSC Scripts is always running as the "System" account. Hence if you have a remote share that implements security you will need to pass
-Credential
parameter that the
Configuration
accepts when you invoke it. The value of
the parameter needs to be a
System.Management.Automation.PSCredential
object
for a user account which does have permissions to access the share.
Configuration EnableIEEsc { Import-DSCResource -Module xSystemSecurity -Name xIEEsc Node $AllNodes.where{ $_.Role.Contains("IE") }.NodeName { xIEEsc EnableIEEscAdmin { IsEnabled = $false UserRole = "Administrators" } xIEEsc EnableIEEscUser { IsEnabled = $false UserRole = "Users" } } } EnableIEEsc -ConfigurationData "configData.psd1" Start-DscConfiguration -Path .\EnableIEEsc -Verbose -Wait -Force
The
Import-DSCResource
CommandLet tells DSC where to find the external code we're calling. And from then on the new
xIEEsc
resource is available to use. Here we're calling it twice to turn off enhanced security for both admin and ordinary users on the server.
With that done we can start to think about the other pre-requisites of Sitecore. So next week's post will look at adapting my previous work on automating a MongoDB install using DSC.
↑ Back to top