All right, let's take a deeper look at playbooks in Ansible on Windows.
I'm back to my WSL2 environment and Visual Studio Code.
This time I brought up the integrated terminal.
And as you can see, the integrated terminal,
when you have the WSL extension,
allows you to tap right into that terminal from VS Code.
I don't know why I didn't think of that earlier.
So we're looking at a playbook I've called test_pb,
and I want to formally denote this one‑to‑many
relationship between the playbook and the plays.
I think I fumbled, pun intended, the definition in the previous demo.
So the root level names here are the plays,
and you can have one‑to‑n number of those grouped
according to whatever your preferences are.
Besides the descriptive play name, we specify using hosts.
These could be individual IP addresses or DNS names,
or it could be host groups within our inventory,
which is what the case is right here.
Some other options you can take with the hosts directive is you can target
the entire inventory by using the reserve all keyword.
If it were going to be the localhost on which the playbook is running,
you can specify that.
But I'm going to reset that back to control.
So once we've established the play and the hosts context,
then we have a list of tasks.
Again, the idea with the YAML is the convention is to do a two‑space indent,
and you just want to be really consistent on the indents here.
Doesn't have to be two spaces.
You just need to be consistent throughout the entire file.
All your indents should be at the same level.
So this first play is going to stop the print spooler service on a machine.
The task itself has a descriptive name,
and then we reference a built‑in or custom module.
Win_service is one of the many built‑in modules.
Let me actually give you a related tip.
If we go back to our terminal, I'm just going to do this in a separate terminal.
Remember, ansible‑doc.
If we do ‑l, that's a long listing.
And then we can grep, for example, for the string ansible.windows,
and that'll give us a report of just the win_ modules.
These are the built‑in modules that I strongly recommend you dive into.
For example, you might wonder win_file, what does that do?
So the next thing to do here is ansible‑doc win_ file.
And you can quickly check out the man page.
In particular, you want to look for the syntax and then any examples.
So we can see using win_file, we can touch a file,
create it if it's not present, remove a file,
create a directory structure, remove.
And all of them behave the same basic way as I've mentioned before.
So in this example, we're doing win_service.
Spooler service needs to be stopped.
Now this is the desired state of the service.
So every time the playbook is executed,
that spooler service on the control nodes will be stopped even if
someone happened to start it in the meantime.
I've also included some additional stuff here just to get you
familiar with it right out of the gate.
We're doing some conditional logic in this example.
As you can see, the when directive uses one of the many Ansible facts,
ansible_os_family,
such that this is only going to execute if the OS on the system is Windows.
So even in the control group, we had some rogue macOS units or macOS nodes,
then this would not error out.
We also could do error handling.
But in this case, it's just going to affect Windows machines,
and that's what we want.
Now you may be wondering what's going on with this
register and notify and handlers?
Register, remember, is where you can for debugging purposes particularly,
you can capture the output of a task.
So in this case, after the task runs,
we're going to gather the spooler status and register that
as a variable called spooler_status.
And then what we can do is notify once that's finished.
And notify is an example of a handler.
So in this case,
I've defined separately in my handlers list a handler called Log Spooler Stop.
This invokes the win_shell built‑in module,
which allows you to simply run a command in the certain location in spot.
In this case, it's going to do echo Spooler service stopped,
and it's going to store the file in the root of C drive under spooler_log.
All right, so I'm just showing you different techniques here,
just bread and butter techniques that go into playbooks.
Let's look at the second play where we set an environment variable.
Again, it's the same idea.
We name the play, we scope it, and then we've got our tasks list.
Our tasks are going to each have a name.
In this case, we've got two,
one called set environment variable that uses the win_environment module to
ensure that the projectX variable has the value of foo.
And it's going to be available on our target nodes at the machine scope.
We're also taking the results of that as a register.
Now I admit I'm going overboard with the debug and the register
just to make a point that it's an option.
We don't have to do tags.
We don't have to do register.
We don't have to do conditional logic.
But I just want to make sure to use these as much as
possible to get you comfortable with it.
So the tags are, again, you can name these however you see fit.
But the tags are also another nice way to do conditional
execution because when we do ansible‑playbook,
I'll show you down here.
When we do ansible‑playbook test_pb, we can do tags,
and then I can do env_variable.
And this is going to run the playbook,
but only those tasks that have the appropriate tag.
So again, in terms of ground‑level best practices,
get into the habit of intelligently tagging your tasks.
Get into the habit of scoping your tasks dynamically
where necessary with conditional logic, as I've mentioned up here.
And particularly when you're troubleshooting,
remember that you can capture task output with register,
and then you can notify a handler to take some other
action when that task completes.
That's what's going on here.
The final task in this playbook is going to create an autostart index.
It's going to print autostarts.
And here I've decided to use win_shell where I could just run
a PowerShell pipeline that runs into the win32 startup
command that grabs out some columns, formats the list,
and then creates a file in the root of each host.
It's called c:\autostarts.txt.
And once again, just because I can,
I've thrown in some examples of conditional logic.
So,
the only machines that'll print autostarts in this example
are ones, hosts, that have Windows PowerShell version 5.0
or higher on those machines.
As far as debug is concerned, we've got a separate task here.
Again,
the nice thing about these debug blocks is that you can add them and
subtract them pretty unobtrusively from your files.
And this is just going to grab that registered autostarts
result object and display the value of that variable when
autostarts result is defined.
In other words when the files created and when it's populated,
we're going to take a look at it.
That making some semblance of sense I hope?
So now when it comes to actually running this command,
I'm going to actually do‑‑‑ Get rid of tags.
Whoops.
I'm going to get rid of tags and show you a quick little check,
a last‑minute preflight check that you can do on your
playbooks before you actually run them to go.
You can add in check, ‑‑check, and you can also throw in verbose output using ‑v.
And the more v's you add, I think four is the maximum,
the more verbose the output.
Just ‑v is going to be the least verbose.
But I use verbose in PowerShell all the time because I
prefer to see extra output as a general rule.
Okay, so it says we've got an error in vault password file loading default.
Hmm.
Vault password must be specified.
Well, if we go over to my Ansible config on this machine,
let's review some of our settings.
Now thus far, you're probably wondering under what context,
under what security context, are our playbooks going to run?
You want to be on top of ansible_user and ansible_password right at the start.
And what I've done,
let me actually bring up my Active Directory Users and Computers,
mmc console on this domain controller.