Now that we’ve created the gold server and version control repositories, it’s time to set up some core services. Since Microsoft Active Directory, DNS, and DHCP are common in the sorts of environments I’m trying to emulate, I’ll use Windows Server Core to set those up here, too. If I was focusing on Windows systems administration, I’d set up multiple domain controllers, multiple DHCP servers, and separate all the critical services. But since I’m focusing on Linux administration and configuration management, I’ll start with Active Directory and DHCP on one virtual machine. If I need to scale out in the future, I can adjust as needed.

I’ll use this as a proof of concept for using Puppet Bolt as an ad hoc change tool on the gold server. This will let me keep the scripts for creating an AD domain, managing DNS records, and managing DHCP options and reservations in version control. It will also let me run those scripts from the gold server, without requiring PowerShell on Linux, Puppet Agent on Windows, or ssh on Windows.

Creating the Domain Controller Virtual Machine

Review the posts on installing pfSense and installing a management virtual machine if necessary, but we’ll be creating a VM for Windows Server.

Create a new virtual machine connected to the lab network. I named mine For Server Core 2016, I used:

  • 2 CPUs
  • 4 GB RAM
  • 40 GB thin-provisioned hard disk

Installing Server Core

In the Windows Server 2016 installation process, a traditional server with a graphical user interface is labeled as “(Desktop Experience)”, and Server Core installations are marked with no additional labels.

Selecting Server Core during the Windows Setup Procesdure

On first boot, Windows will need you to set an administrator password. Do so, and you’ll be at an administrative command prompt. Run sconfig to set the following:

  • Computer Name: dc1
  • Windows Update Settings: Automatic
  • Network Settings:
    • static IP address:
    • subnet mask:
    • gateway:
    • Primary DNS server:
    • Alternate DNS server: none
  • Download and Install Updates: All updates

Installing all the updates will typically require a reboot, which would also cause the new computer name to take effect. There may also be additional rounds of available updates.

Post-Installation Tasks

Installing Bolt and Configuring a Bolt Project

Bolt is Puppet’s agentless orchestration tool, similar to Red Hat’s Ansible. Since Bolt uses Windows Remote Management (WinRM) for communication, and Server Core 2016 enables WinRM by default, there’s nothing additional to do on the Server Core console to prepare for using Bolt.

On the gold server, I followed the Installing Bolt documentation, specifically:

yum install
yum install puppet-bolt

I created a bolt repository in the /opt/gitrepos folder, cloned it to a folder in root’s home directory, and created a Boltdir to hold the bolt project data:

git init --bare --shared=umask /opt/gitrepos/bolt.git
git clone /opt/gitrepos/bolt.git ~/bolt
mkdir ~/bolt/Boltdir

Creating an Active Directory Domain with Bolt

To make a basic Active Directory domain without Bolt, I would start PowerShell on the domain controller virtual machine’s console and run:

install-windowsfeature ad-domain-services
install-addsforest -domainname -installdns

I would then enter the Safe Mode Administrator password twice when prompted, then let the server reboot.

With Bolt, I could run those commands with bolt command run, but I’d rather build a PowerShell script and an associated Bolt task to automate things. I’d also like to create an inventory file to avoid having to specify IP addresses, usernames, protocols, and other connection details each time I run Bolt.

Creating a Bolt Inventory

For the inventory file, I created a Boltdir/inventory.yaml file with the contents:

  - name: windows
    - name: dc
      transport: winrm
        ssl: false
        user: Administrator
          _plugin: prompt
          message: Password

This will let me run things on the domain controller by specifying the --target parameter for bolt. Once DNS is up and running, I can replace the IP address with a hostname.

Once the inventory is created, I can test it by running a built-in task with:

 bolt task run reboot::last_boot_time --targets dc

and seeing the domain controller’s last boot time.

Creating Bolt Tasks

To make my own tasks for creating the Active Directory domain, I created a directory structure for the task:

mkdir ~/bolt/Boltdir/site-modules/renfro/tasks
cd ~/bolt/Boltdir/site-modules/renfro/tasks

and then created a JSON file and a PowerShell script for each task.

The PowerShell script (create_ad_domain.ps1) is like any oher PowerShell script developed and run on Windows, it’s just stored in the Bolt directory tree:

param (
try {
  install-windowsfeature ad-domain-services
} catch {
  Write-Output "Cannot install ad-domain-services windows feature"
  exit 1
try {
  $securepassword = ConvertTo-SecureString $safemodepassword `
    -AsPlainText -Force
  Install-ADDSForest -DomainName $domainname -InstallDns `
    -SafeModeAdministratorPassword $securepassword -Force
} catch {
  exit 2

The JSON metadata file (create_ad_domain.json) contains:

  "puppet_task_version": 1,
  "supports_noop": false,
  "description": "Create Active Directory domain.",
  "parameters": {
    "domainname": {
      "description": "The Active Directory domain name.",
      "type": "Variant[String]"
    "safemodepassword": {
      "description": "The Active Directory Safe Mode administrator password.",
      "type": "Variant[String]"

Consider Running the Bolt Task to Create the Active Directory Domain, but Don’t

Once both files are created, the task could be run from any directory in the bolt project:

bolt task run --targets dc renfro::create_ad_domain safemodepassword=ENTER_PASSWORD_HERE

But we’d like a way to not have a password recorded in the command history. We could get around that by disabling the bash history or deleting specific entries, but it would be better to prompt for the password instead.

We can’t have the PowerShell script prompt for input, and tasks are limited in how they can interact with users, so we have to go one step farther and set up a Bolt plan.

Writing a Bolt Plan Wrapper Around the Active Directory Domain Creation Task

Plans are typically used for higher-level orchestration across tasks and hosts, but we’ll use it here because it has a prompt function that can hide sensitive input like passwords. Sensitive results from the prompt command can be converted back to plain text for running tasks using the unwrap method.

The plan wrapper is pretty short, just enough to accept one parameter from the command line, prompt for another parameter, and call the task. In Boltdir/site-modules/renfro/plans/create_ad_domain.pp, add the following:

plan renfro::create_ad_domain(
  TargetSpec $dc,
  String $domain,
) {
  $safemodepassword = prompt('Enter the safe-mode administrator password',
                             'sensitive' => true).unwrap
  run_task('renfro::create_ad_domain', $dc, domainname => $domain,
           safemodepassword => $safemodepassword)

Run this plan with:

bolt plan run renfro::create_ad_domain dc=

and you’ll be prompted for the administrator password for the domain controller (with a prompt of Password:), and prompted for the safe-mode administrator password for Active Directory (with a prompt of Enter the safe-mode administrator password:). It’ll take a while for this task to complete. If you have a view of the domain controller’s console, you’ll see it reboot after the task completes.

Testing the Active Directory Domain

Once the domain controller has rebooted, verify that Active Directory is functional by running:

bolt command run "Get-Aduser Administrator" --target dc
bolt command run "Get-Adcomputer dc1" --target dc

from any directory in the bolt project. Both commands should return objects with distinguished names and object classes.