Learning PowerShell DSC - Sample Chapter
Learning PowerShell DSC - Sample Chapter
pl
e
E x p e r t i s e
D i s t i l l e d
P U B L I S H I N G
Sa
P r o f e s s i o n a l
James Pogran
ee
James Pogran
P U B L I S H I N G
15 years. His first job was systems administration for a large military installation. He
then moved on to develop monitoring software and automate large scale Windows
environments for a major managed services provider. He is currently a software
engineer at Puppet Labs where he helps to make Windows automation even better
with Puppet.
Preface
Windows PowerShell was a transformative event for the Windows management
ecosystem. It marked a shift from the GUI-based administration of "click next,
next, finish" to a composable command line experience that can be scripted and
automated. This methodology was not accepted immediately by the Windows
community, but time has proven the approach viable and PowerShell is now an
integral part of any systems administrator's toolkit.
Windows PowerShell Desired State Configuration (DSC) marks another shift in
Windows administration, but this time, it is a move away from the run-once scripts
that cannot detect the existing state to declarative and repeatable automation without
side effects. While PowerShell enabled an automation paradigm that was previously
unmatched on Windows systems, crafting truly dependable automation took many
lines of boilerplate code of exception catching and state checking. DSC handles this
boilerplate code and gives you a clean and readable way to declare the expected state
of your systems without worrying about how those systems are configured.
Whether you manage a few servers or several thousands of them, the same problems
occur repeatedly. How do you ensure that all the servers under your care are
configured to the exact specifications? How do you write those specifications down
so that not only you and your coworker but also the machine understands them?
This seemingly conflicting set of requirements is the purpose of DSC. Using DSC,
you can write the human-readable desired state of the system you expect, and DSC
ensures that the state of the system is what you desired it to be.
Preface
In this book, we will introduce the configuration management concepts that DSC
uses to accomplish these feats. We then cover the architecture of DSC, which allows
us to specify the state of a target system without having to code the implementation
details ourselves. From there, we will cover how to create files that can be read by
both DSC and humans to ensure that the state of target systems is what we specify.
We will then address how to customize DSC to administer our customized and
unique environments, and then walk through the ways in which we can deploy
these configurations to the target systems using the different deployment models of
DSC. We will wrap up with a walkthrough of a typical deployment cycle of example
software using real-world problems and solutions.
Tooling
Setting things up
Tooling
DSC supports applying DSC configurations locally and remotely using the
Start-DscConfiguration Cmdlet. This Cmdlet handles both copying the MOF
file to the target node and telling DSC to execute the MOF on the target node.
Start-DscConfiguration can be invoked interactively as well as run in the
background. Interactive executions are run as we are watching and are able to show
us verbose output as each step in the DSC configuration happens. Background
executions are PowerShell jobs that do not block your shell, so that you can do
other things while the job runs. This means you can use this Cmdlet to push a DSC
configuration and then walk away or continue to use your current PowerShell
console session to do other things as it executes on the target node.
[ 153 ]
All output from the DSC configuration execution is logged by DSC, and the StartDscConfiguration Cmdlet can show this information, if configured. By default, it
only displays error and completion information, but it can be configured to yield
verbose output by using the Verbose parameter.
Setting things up
Up until now, you could have gotten away with just looking at the examples in the
book without running them or adapting them slightly to work on your desktop or a
test VM or server. At this point, it is really helpful to have a test VM that you can create
and destroy several times over as you work through the examples. This is especially
true when we try executing DSC configurations remotely later in the chapter.
Test environments
There are several approaches you can take when building and using test
environments. If you have the spare physical machines, you can set up several
machines as test servers to run these configurations. This is the easiest way if you
have the extra hardware lying around, but it becomes difficult when you want to
treat them as disposable machines. Constantly reinstalling the machines will become
tedious without imaging software, so keep in mind the time cost this entails if you go
this route.
A far easier route is Virtual Machines (VM), which are commonplace is today's
world. HyperV, VMware, and Parallels all have options to run VMs on your desktop
or laptop. This allows you to have dependable and repeatable tests on known
configurations so that you can keep testing your DSC configurations until you are
sure they work.
[ 154 ]
Chapter 5
While each of these vendors' software has the functionality to allow you to perform
an operation and then "roll back" to a known good state, it is sometimes slow and
cumbersome to use these features. They were designed with infrequent use in
mind. We are looking for something that can be brought up in minutes just as easily
as it can be destroyed. A good tool to look into using for this purpose is Vagrant
(https://www.vagrantup.com/). Setting up and using Vagrant is beyond the scope
of this book, but there are a good many resources out there to get you started. It will
make your life a lot easier to use tools like Vagrant, VMware, or HyperV to automate
your development environments.
For the purposes of this book, it is not necessary to have these setups specifically, but
we will assume that you are at least using a test machine on which you can run these
commands time and time again while you get used to these operations.
We are going to build two test computers and call them box1 and box2. We will use
box1 both as the place where we make the DSC configuration and the place where
we execute it from. The second test computer, box2, will be our target node that
receives DSC configurations from us so that we can demonstrate remote operations.
Because of this, both computers need to be able to access each other on the network,
so configure your IP addresses appropriately.
It is a modified version of what you would see if you used the Get-Help Cmdlet
because we have omitted extra parameters to better illustrate what we are doing in
this chapter. This is significantly simpler than what we will be dealing with when we
push DSC configuration files remotely, so it is a good place to start.
[ 155 ]
Start-DscConfiguration accepts a -Path variable where the MOF file will be and
a Boolean parameter called -Wait, along with CommonParameters such as Verbose
and Debug. The Wait parameter is the indication that we want the job to run in front
of us and only return when the DSC execution is completed. Any errors during
execution will be presented to us during the execution run, but no other information.
For this reason, when using Wait it is commonplace to use Verbose as well, so that
the running output from the DSC configuration is presented to you as it happens.
We'll be using this combination as we proceed for this very reason, but we may not
include all the output text as it can become quite verbose (pun intended).
In our example script, we will create a text file on the primary drive with dummy
contents. Again, we are purposely not fancy here in order to focus on how to push
DSC configurations and not the DSC configurations themselves:
###begin script####
Configuration SetupAllTheThings
{
Node "localhost"
{
File CreateFile
[ 156 ]
Chapter 5
{
Ensure
= "Present"
DestinationPath = "c:\test.txt"
Contents
= "Wakka"
}
}
}
Compiling the preceding DSC configuration script gives us a valid MOF file that we
can use on the local computer. This should all be straightforward for you by now,
and you should see the following results from the compiling of the MOF file:
PS C:\Users\Administrator> C:\vagrant\SetupAllTheThings.ps1
Directory: C:\vagrant\SetupAllTheThings
Mode
LastWriteTime
Length Name
----
-------------
------ ----
-----
7/01/2015
3:19 PM
1276 localhost.mof
If that doesn't compile correctly, look at your syntax and ensure it's the same as what
we have laid out here. It may help if you add the Verbose parameter to your DSC
configuration function to see what is going on as the MOF is compiled. If you are
still having trouble, or want to see even more information, then you can set your
$DebugPreference to Continue and receive even more diagnostic information.
Example output is as shown:
DEBUG:
DEBUG:
MSFT_PackageResource:
DEBUG:
MSFT_PackageResource:
DEBUG:
MSFT_PackageResource:
DEBUG:
MSFT_PackageResource:
DEBUG:
MSFT_PackageResource:
DEBUG:
MSFT_PackageResource:
'The Thing'
DEBUG:
MSFT_PackageResource:
MSFT_PackageResource:
DEBUG:
MSFT_PackageResource:
'c:\allthethings\thing.msi'
DEBUG:
MSFT_PackageResource:
DEBUG:
MSFT_PackageResource:
DEBUG:
MSFT_PackageResource:
Canonicalized property 'ProductId'
= '{8f665668-7679-4f53-acde-06cf7eab5d73}'
It's sometimes hard to see the exact error in the DSC output, so get used
to using PowerShell to inspect the built-in $error array to drill down
and find the exact information you need, as shown:
[PS]> $error[0] | Format-List -Property * -Force
If we examine the localhost.mof file, we will see that the file resource laid out
applies to a target node named localhost. This means that, although we compile
this MOF file on the computer we are sitting on, this MOF file can be pushed to any
computer, whether locally or remotely, because localhost applies to any computer.
If we wanted to restrict which target nodes used specific MOF files, we would need
to specify their host names in the Node block. We will cover this when we deal with
pushing DSC configurations remotely shortly.
[ Start
Set
[ 158 ]
Chapter 5
VERBOSE: [BOX1]: LCM:
[ Start
Resource ]
[[File]CreateaFile]
[ Start
Test
[[File]CreateaFile]
VERBOSE: [BOX1]:
system cannot find the file specified.
[[File]CreateaFile] The
VERBOSE: [BOX1]:
related file/directory is: c:\test.txt.
[[File]CreateaFile] The
[ End
Test
[[File]CreateaFile]
[ Start
Set
[[File]CreateaFile]
in
VERBOSE: [BOX1]:
system cannot find the file specified.
[[File]CreateaFile] The
VERBOSE: [BOX1]:
related file/directory is: c:\test.txt.
[[File]CreateaFile] The
[ End
Set
[[File]CreateaFile]
[ End
Resource ]
[[File]CreateaFile]
[ End
Set
[ End
Set
in
in
0.0790 seconds.
We get a lot of information back, so let's go over what we are seeing. It initially tells
us that it's performing a DSC operation and the methods it's using to invoke. This is
where we see that the Start-DscConfiguration Cmdlet is just a wrapper over the
CIM methods that actually execute the DSC functions. You won't need to know these
CIM methods for everyday use, but it's interesting to see the inner workings shown
here. The next line is telling us that all the subsequent lines come from our target
node box1 and were initiated by a user with this SID. It doesn't look so important
here as we're running on the same box we've compiled the MOF on; however, when
we deal with remote pushes and note which target node had which operations occur
on it, it becomes very important. The rest of the lines tell us that the file did not exist
and that DSC had to create it. Not so hard, is it?
[ 159 ]
[ 160 ]
Chapter 5
You may not be familiar with CimSessions as much as you are with PSSession, but
they both describe remote sessions on a target node. What is important to know for
this chapter is that CimSessions cannot execute arbitrary code like a PSSession can; it
executes specific CIM methods on a remote target node. Start-DscConfiguration
and other DSC Cmdlets are mostly wrappers around CIM methods. This is not a
limitation for CimSessions, as working this way takes up fewer system resources on
the target node.
You create CimSessions by using the New-CimSession Cmdlet and storing the result
in a variable that you can pass to the Start-DscConfiguration Cmdlet:
[PS]> $cred = Get-Credential
[PS]> $session = New-CimSession ComputerName "foo" Credential $cred
As you can see, the New-CimSession Cmdlet accepts a computer name and a
PSCredential object. We can also create many CimSessions at the same time by
passing in an array of computer names, as follows:
[PS]> $session = New-CimSession ComputerName "foo","Bar","baz","wakka"
Credential (Get-Credential)
Now that we know how to connect to a target node, we can start setting up the
environment.
[ 161 ]
[N] No
[S] Suspend
PS C:\Users\Administrator>
In box2, we run the following commands. Modifying the built-in firewall is only
necessary in a test environment to make life easier, and is not recommended in a
production environment. In a production environment, you would have already
allowed rules in your firewall setup for PSRemoting, so this should not be necessary.
The second command enables PSRemoting. DSC doesn't require PSRemoting exactly;
it requires WinRM to work against remote target nodes. We use the commands to
enable PSRemoting because those commands configure and start up WinRM for
us without any extra effort on our part. Starting with Windows 2012, PSRemoting
comes enabled by default, so it really is not much of an imposition to expect both to
be configured on target nodes.
PS C:\Users\Administrator> Set-NetFirewallProfile -All -Enabled False
PS C:\Users\Administrator> Enable-PSRemoting -Force
WinRM is already set up to receive requests on this computer.
WinRM is already set up for remote management on this computer.
We can test connections from box1 to box2 by creating a CimSession using the
commands we covered earlier:
[PS]> $cred = Get-Credential administrator
[PS]> $session = New-CimSession ComputerName "192.168.0.13" Credential
$cred
If errors occur, then check your network connectivity and permissions. Consult the
PowerShell Remoting documentation for more information on how to troubleshoot
PSRemoting connections.
[ 162 ]
Chapter 5
Directory: C:\vagrant\SetupAllTheThings
Mode
LastWriteTime
Length Name
----
-------------
------ ----
-----
7/01/2015
3:19 PM
1276 localhost.mof
We want to now apply this same DSC configuration to another computer, without
actually logging into that computer ourselves. Creating the CimSession as we did
earlier is not enough. That allows us to connect to the remote host, but we need
a way to tell DSC that we want this DSC configuration file to be executed on that
target node. If we recall our previous chapters on DSC configurations and DSC
configuration data, we know that we can create a configuration data section with
the relevant information on our target nodes and not have to change our DSC
configuration much at all. We'll add a DSC configuration data hash and add the
information for box1 and box2 to it now. Then, we'll modify the Node block and use
the $AllNodes variable to pass the node information to the DSC configuration:
###begin script###
$data = @{
AllNodes = @(
@{ NodeName = 'localhost' },
@{ NodeName = '192.168.0.13' }
)
}
Configuration SetupAllTheThings
{
Node $AllNodes.NodeName
{
File CreateFile
{
[ 163 ]
= "Present"
DestinationPath = "c:\test.txt"
Contents
= "Wakka"
}
}
}
Compiling that DSC configuration into an MOF file in box1 results in the
following output:
PS C:\Users\Administrator> C:\vagrant\SetupAllTheThings.ps1
Directory: C:\vagrant\SetupAllTheThings
Mode
LastWriteTime
Length Name
----
-------------
------ ----
-----
8/1/2015
1:44 AM
1234 localhost.mof
-----
8/1/2015
1:44 AM
1240 192.168.0.13.mof
We now have two MOF files, one for the localhost (box1) and one for 192.168.0.13
(box2). Looking at 192.168.0.13.mof, we see that it has information for box2
inside it. This is the information that Start-DscConfiguration needs in order to
determine which target nodes need to be pushed to the DSC configuration. The
CimSessions give us the transport to the target nodes, but the MOF files give us
the destinations.
[ 164 ]
Chapter 5
Again, we use CimSession here because sometimes you need special credentials,
authentication, or other options in order to connect to remote target nodes. You can
omit creating a separate CimSession here if you so wish:
PS C:\Users\Administrator> Start-DscConfiguration -Path C:\vagrant\
SetupAllTheThings -Verbose -Wait Force CimSession $session
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters,
''methodName' =
SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'n
amespaceName' =
root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: Perform operation 'Invoke CimMethod' with following parameters,
''methodName' =
SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'n
amespaceName' =
root/Microsoft/Windows/DesiredStateConfiguration'.
VERBOSE: An LCM method call arrived from computer BOX1 with user sid S-15-21-1953236517-242735908-2433092285-500.
VERBOSE: An LCM method call arrived from computer BOX1 with user sid S-15-21-1953236517-242735908-2433092285-500.
VERBOSE: [BOX2]: LCM:
[ Start
Set
[ Start
Set
[ Start
Resource ]
[[File]CreateaFile]
[ Start
Test
[[File]CreateaFile]
VERBOSE: [BOX2]:
system cannot find the file specified.
[[File]CreateaFile] The
VERBOSE: [BOX2]:
related file/directory is: c:\test.txt.
[[File]CreateaFile] The
[ End
Test
[[File]CreateaFile]
[ Start
Set
[[File]CreateaFile]
in
VERBOSE: [BOX2]:
system cannot find the file specified.
[[File]CreateaFile] The
VERBOSE: [BOX2]:
related file/directory is: c:\test.txt.
[[File]CreateaFile] The
[ End
Set
[[File]CreateaFile]
[ End
Resource ]
[[File]CreateaFile]
[ End
Set
[ End
Set
[ 165 ]
in
0.0000 seconds.
in
[ Start
Resource ]
[[File]CreateaFile]
[ Start
Test
[[File]CreateaFile]
VERBOSE: [BOX1]:
[[File]CreateaFile] The
destination object was found and no action is
required.
VERBOSE: [BOX1]: LCM:
0.0160 seconds.
[ End
Test
[[File]CreateaFile]
[ Skip
Set
[[File]CreateaFile]
[ End
Resource ]
[[File]CreateaFile]
[ End
Set
[ End
Set
in
in
0.0630 seconds.
If all went well, you should see the preceding output on your test system. Notice
in the output that both box2 and box1 had DSC configurations applied to them.
You can also see that we have more output for box2 than we have for box1. That is
because the file was not present in box2 while it was present in box1. This is a great
example of the benefit of CM products like DSC; it detected that the file was already
present on box1 and did not need to touch it while noticing that it was not present on
box2 and creating it. Both actions were easily discovered in the log.
Also notice that we get the same format logging as we do when pushing DSC
configurations locally even though we are operating on remote target nodes.
Each log has the node that it is coming from, so it is easy to see which ones are for
which hosts. This is useful for our Verbose messages but even more useful for any
ErrorMessages that come from our executions. If we received an error during
execution, it would look like this:
[PS]> $error[0] | fl * -for
writeErrorStream
: True
PSMessageDetails
OriginInfo
: REMOTE-TEST-COMPUTER
Exception
A configuration is
: Microsoft.Management.Infrastructure.CimException:
...
[ 166 ]
Chapter 5
The OriginInfo property tells us which target node is sending us the error
information. Of course, DSC logs all this information on the remote host, but it is very
useful to receive it interactively as you test and develop your DSC configurations.
Something that we glossed over is that we pointed Start-DscConfiguration to
a folder containing more than one MOF file and it executed each MOF file on the
respective target nodes with the correct credentials. How did it know to do that?
This is a potentially powerful scenario.
Since Start-DscConfiguration uses WinRM as the transport mechanism, it inherits
all the power and flexibility of the command structures provided by WinRM. One
of these is the ability to connect to many target nodes and execute a command at the
same time. While we may have seen the output from box1 and box2 sequentially,
the DSC execution jobs were performed in parallel or on both machines at once.
The amount of work done in the DSC configuration file for each target node will
determine how long the executions take on each node. Notice that box2 finished
earlier because it had less to do than box1. It only seemed like each target node was
operated on sequentially because we used the Wait parameter, which indicated to
Start-DscConfiguration to only return control to the console session when all
jobs have completed. You can imagine the usefulness when you can apply this to
hundreds of machines at the same time.
[ 167 ]
When pushing DSC configurations, DSC does not copy DSC Resources for you.
While you may be annoyed by this, it does make sense when you think about it. DSC
Resources can be placed at several places, and there isn't a file transfer mechanism
that can be used to put them there without involving the user in making a choice or
providing credentials. You also might be wondering why we need to copy over the
DSC modules if we already compiled the MOF locally. The MOF compilation process
produces the manifest that DSC uses to list the things to do on the target node; the DSC
Resource holds the code that implements the decisions and actions to bring the system
to the desired state, so it has to be present on the target node for it to be executed.
The only way to solve this is to copy the DSC Resources over to the target nodes
yourself when pushing DSC configurations. If you are thinking of using DSC to do
this, that's good inventive thinking, but this is not so easy a problem to solve using
DSC. It's a chicken-and-egg scenario. DSC can't do this for you because it uses the
DSC Resources present on the machine to validate that it can execute the incoming
DSC configuration you are pushing to it. It parses the MOF before it executes it and
tries to find out if all the needed DSC Resources are present on the host. If they aren't
present, then it exits with an error.
When copying DSC Resources, you will have to deal with the normal file copying
problems of which transfer method to use (network or SMB shares, credentials,
and so on). If you are copying new DSC Resources, then you may have to deal
with different versions. Each DSC Resource comes with version information in its
manifest file, but may include or remove files at its own discretion. A blind file copy
will not know to remove files or deal with file renames, among other possible version
scenarios. Using PackageManagement (as described in Chapter 4, DSC Resources)
or a file-sync tool will help alleviate this issue somewhat, but at the same time
introduces steps you have to perform on the remote target nodes before you can
push DSC configurations to them. This means setup steps that you have to either
script or manually follow, determine drift, and so on. Dealing with this via scripting
(deleting if present, reading version files, and so on) adds more complexity to your
deployments and makes you have to do the work of detecting drift, when you
started out having DSC do this for you.
This is somewhat alleviated in DSC v5, where different DSC Resource versions can
coexist side-by-side. Details of how this works are still considered experimental,
so whether this will help in pushing DSC configurations or only help Pull Server
scenarios remains to be seen.
[ 168 ]
Chapter 5
The only way to cheat and accomplish this using DSC is to have two separate DSC
configuration script files. One copies the DSC Resources, and the other contains
the real manifest, but the same problems above apply here. In WMF 5, a potential
solution to this problem exists. You can use a DSC Pull Server to distribute DSC
Resources while still pushing DSC configurations directly to the target nodes.
With all these problems, it is evident that this is not meant to be a scalable long-term
operation for you to perform yourself on many thousands of hosts, and that a pull
server was meant to handle this for you.
Summary
And there you have it: pushing DSC configurations! We have covered how to push
them locally and how to use them remotely for both one machine and many. We
looked at how to push DSC configurations sequentially and all at once. We also
covered the potential drawbacks to the push model and the steps you must take in
pushing DSC configurations that you do not have to with DSC Pull Servers.
In the next chapter, we will cover how to set up DSC Pull Servers and how to use
DSC configurations with them.
[ 169 ]
www.PacktPub.com
Stay Connected: