As you already know we introduced PowerShell Desired State Configuration to the world at our TechEd NA 2013 Session. The session also introduced the notion of structural configuration (what) and environmental configuration (where) (at the 25:50 min mark). Structural configuration defines what is needed and does not change based on the environment. For instance, a configuration can require IIS to be installed – whether we have one node, or many nodes, whether it is a development environment or a production environment. It does not matter - we need IIS installed. Environmental configuration defines the environment in which the configuration is deployed. For instance, the node names and source file locations can change from a development to a test to a production environment.
DSC offers the ability to separate structural configuration from environment configurational. This provides the ability to scale up or down a configuration across machines.
The way to specify environmental configuration is through the ConfigurationDataautomatic parameter which is a hash table. Alternatively, the parameter can also take a .psd1 file containing the hashtable
PS C:\> Get-Command MyConfiguration -Syntax
MyConfiguration [[-InstanceName]< string>] [[-OutputPath] <string>] [[-ConfigurationData]< hashtable>]
This hash table needs to have at least one key AllNodes– whose value is structured. ConfigurationData can have any number of additional keys and value mappings added. For example:
$MyData=
@{
AllNodes = @();
NonNodeData =""
}
The Key of interest is AllNodes. Its value is an array. However each element of the array is a hash table in itself with NodeName being a required key
$MyData=
@{
AllNodes =
@(
@{
NodeName ="Nana-VM-1"
},
@{
NodeName ="Nana-VM-2"
},
@{
NodeName ="Nana-VM-3"
}
);
NonNodeData =""
}
Each hash table entry in AllNodes corresponds to configuration data for one node in the configuration. The hash table can have any number of other keys (other than the required NodeName key)
$MyData=
@{
AllNodes =
@(
@{
NodeName ="Nana-VM-1";
Role ="WebServer"
},
@{
NodeName ="Nana-VM-2";
Role ="SQLServer"
},
@{
NodeName ="Nana-VM-3";
Role ="WebServer"
}
);
NonNodeData =""
}
DSC provides 3 special variables for use within the configuration which can access elements from the configuration data.
1. $AllNodes: Itis a special variable that will refer to the AllNodes collection. It also supports filtering using simplified .Where() and .ForEach() syntax. So you can author a configuration like this
configurationMyConfiguration
{
node $AllNodes.Where{$_.Role -eq"WebServer"}.NodeName
{
}
}
When the configuration is invoked with the ConfigurationData parameter the filter returns a set of nodes for use in the node statement. This avoids the need for having to hard code the node name in a configuration (or parameterizing it always). This is equivalent to writing the following (When the above configuration is invoked with –ConfigurationData$MyDatapresented above):
configurationMyConfiguration
{
node Server1
{
}
node Server3
{
}
}
2. $Node: Once a set of nodes is filtered from $AllNodes, $Nodecan be used to refer to the current entry.
configurationMyConfiguration
{
Import-DscResource-ModuleNamexWebAdministration-NameMSFT_xWebsite
node $AllNodes.Where{$_.Role -eq"WebServer"}.NodeName
{
xWebsite Site
{
Name =$Node.SiteName
PhysicalPath =$Node.SiteContents
Ensure ="Present"
}
}
}
The above configuration is equivalent to writing the following (when evaluated with $MyDatapresented above)
configurationMyConfiguration
{
Import-DscResource-ModuleNamexWebAdministration-NameMSFT_xWebsite
node Nana-VM-1
{
xWebsite Site
{
Name ="Website1"
PhysicalPath ="C:\Site1"
Ensure ="Present"
}
}
node Nana-VM-3
{
xWebsite Site
{
Name ="Website3"
PhysicalPath ="C:\Site3"
Ensure ="Present"
}
}
}
Note: xWebsite is a resource that we published as part of DSC Resource Kit Wave 1. More information on the same can be found here
If you want some properties to apply to all the nodes, then it can be specified with NodeName as “*” (Note: * is a special notion. Wildcards are not supported)
$MyData=
@{
AllNodes =
@(
@{
NodeName ="*"
LogPath ="C:\Logs"
},
@{
NodeName ="Nana-VM-1";
Role ="WebServer"
SiteContents ="C:\Site1"
SiteName ="Website1"
},
@{
NodeName ="Nana-VM-2";
Role ="SQLServer"
},
@{
NodeName ="Nana-VM-3";
Role ="WebServer";
SiteContents ="C:\Site2"
SiteName ="Website3"
}
);
}
Now every node has a LogPath property.
3. $ConfigurationData: This variable can be used from within a configuration to access the configuration data hash table passed as a parameter.
$MyData=
@{
AllNodes =
@(
@{
NodeName ="*"
LogPath ="C:\Logs"
},
@{
NodeName ="Nana-VM-1";
Role ="WebServer"
SiteContents ="C:\Site1"
SiteName ="Website1"
},
@{
NodeName ="Nana-VM-2";
Role ="SQLServer"
},
@{
NodeName ="Nana-VM-3";
Role ="WebServer";
SiteContents ="C:\Site2"
SiteName ="Website3"
}
);
NonNodeData =
@{
ConfigFileContents = (Get-ContentC:\Template\Config.xml)
}
}
configurationMyConfiguration
{
Import-DscResource-ModuleNamexWebAdministration-NameMSFT_xWebsite
node $AllNodes.Where{$_.Role -eq"WebServer"}.NodeName
{
xWebsite Site
{
Name =$Node.SiteName
PhysicalPath =$Node.SiteContents
Ensure ="Present"
}
File ConfigFile
{
DestinationPath =$Node.SiteContents +"\\config.xml"
Contents =$ConfigurationData.NonNodeData.ConfigFileContents
}
}
}
Here is a complete example using configuration data (already included in examples for xWebAdministration module in DSC Resource Kit Wave 1)
configurationSample_xWebsite_FromConfigurationData
{
# Import the module that defines custom resources
Import-DscResource-ModulexWebAdministration
# Dynamically find the applicable nodes from configuration data
Node $AllNodes.where{$_.Role -eq"Web"}.NodeName
{
# Install the IIS role
WindowsFeature IIS
{
Ensure ="Present"
Name ="Web-Server"
}
# Install the ASP .NET 4.5 role
WindowsFeature AspNet45
{
Ensure ="Present"
Name ="Web-Asp-Net45"
}
# Stop an existing website (set up in Sample_xWebsite_Default)
xWebsite DefaultSite
{
Ensure ="Present"
Name ="Default Web Site"
State ="Stopped"
PhysicalPath =$Node.DefaultWebSitePath
DependsOn ="[WindowsFeature]IIS"
}
# Copy the website content
File WebContent
{
Ensure ="Present"
SourcePath =$Node.SourcePath
DestinationPath =$Node.DestinationPath
Recurse =$true
Type ="Directory"
DependsOn ="[WindowsFeature]AspNet45"
}
# Create a new website
xWebsite BakeryWebSite
{
Ensure ="Present"
Name =$Node.WebsiteName
State ="Started"
PhysicalPath =$Node.DestinationPath
DependsOn ="[File]WebContent"
}
}
}
# Content of configuration data file (e.g. ConfigurationData.psd1) could be:
# Hashtable to define the environmental data
@{
# Node specific data
AllNodes = @(
# All the WebServer has following identical information
@{
NodeName ="*"
WebsiteName ="FourthCoffee"
SourcePath ="C:\BakeryWebsite\"
DestinationPath ="C:\inetpub\FourthCoffee"
DefaultWebSitePath ="C:\inetpub\wwwroot"
},
@{
NodeName ="WebServer1.fourthcoffee.com"
Role ="Web"
},
@{
NodeName ="WebServer2.fourthcoffee.com"
Role ="Web"
}
);
}
# Pass the configuration data to configuration as follows:
Sample_xWebsite_FromConfigurationData-ConfigurationDataConfigurationData.psd1
Separating the environmental configuration from structural configuration helps author configuration without having to “hard code” environment specific information in a configuration declaration. DSC provides a mechanism to do so but does not enforce it. The separation can be very specific to a configuration and its environment and each configuration author can use it as they seem fit.
Happy configuring !!!
Narayanan (Nana) Lakshmanan
Development Lead - PowerShell DSC