Jinja2 and… Powershell? Automation(ish) Microsoft DHCP


Most of us have home labs, right?

I’m in the middle of doing some zero touch provisioning testing, and I had the need to create a bunch of DHCP scopes and reservations, some with scope specific options, and some with client specific options. As often as I’ve had to create a Microsoft DHCP server in the lab and set up some custom scopes, I decided I was going to figure out how to automate this as much as I could with a little effort as possible.

After taking a quick look around for a python library to help me out, python being my weapon of choice, I realized that I was going to have to get into some Powershell scripting. I’ve dabbled before, but I’ve never really take the time to learn much about Powershell control structures ( loops, conditionals, pipes, etc…).  I really didn’t want to spend the time getting up to speed on a new language, so I instead decided I was going to use the python skills I had to auto generate the scripts using a little jinja2 and some google-technician skills.

Figuring out the Powershell Syntax

This was the easy part actually, Microsoft has some pretty great documentation for Powershell CmdLets and there was more than a couple of blogs out there with examples, Unfortunately, I didn’t take notes on all the posts I went through… yeah, I suck, but I offer thanks to everyone

Creating the Scopes

The Jinja Template for Creating Scopes

Once I figured out the specific syntax that I needed to generate the DHCP scopes with the proper scope options, I dropped the syntax into a Jinja template using the For loop to run over multiple scopes as defined in the YAML file ( see the next GIST ).


#File to use powershell to automatically build all required DHCP scopes for lab environment
Add-DhcpServerv4OptionDefinition -Name tftp -OptionId 150 -Type IPv4Address
{% for scope in dhcpscopes.dhcpscopes %}
Add-DHCPServerv4Scope -EndRange {{ scope.EndRange }} -Name {{ scope.Name }} -StartRange {{ scope.StartRange }} -SubnetMask {{ scope.SubnetMask }} -State Active
Set-DHCPServerv4OptionValue -ComputerName {{ dhcpscopes.lab_globals.Server }} -ScopeId {{ scope.ScopeID }} -DnsServer {{ dhcpscopes.lab_globals.DNS }} -DnsDomain {{ dhcpscopes.lab_globals.Domain }} -Router {{ scope.Gateway }}
Set-DhcpServerv4OptionValue -ComputerName {{ scope.Server }} -ScopeId {{ scope.ScopeID }} -OptionId 66 -Value {{ dhcpscopes.lab_globals.tftp }}
Set-DhcpServerv4OptionValue -ComputerName {{ scope.Server }} -ScopeId {{ scope.ScopeID }} -OptionId 67 -Value {{ scope.InitialConfig }}
Set-DhcpServerv4OptionValue -ComputerName {{ scope.Server }} -ScopeId {{ scope.ScopeID }} -OptionId 150 -Value {{ dhcpscopes.lab_globals.CCM }}
{% endfor %}

The YAML file to define the Scopes

I chose to use YAML to define the inputs because well, that’s what I felt like working in at the time and it also allowed me to separate out the global Values from those specific to each scope. As I move forward in my full home lab automation project, I’m thinking I might use a single globals values YAML file to hold all the global values for everything in the entire infrastructure, but for now, I decided to keep things simple and just include it in the same YAML file.

If you take a look at the GIST below, you should be able to easily identify what each of the different elements are for.


lab_globals:
{tftp: 10.101.0.203,
DNS: 10.101.0.20,
Domain: lab.local,
CCM: 10.101.0.1,
Server: 10.101.0.20}
dhcpscopes:
{Server: 10.101.0.20,
ScopeID: 10.11.0.0,
Name: MobileFirstVlan1,
StartRange: 10.11.0.100,
EndRange: 10.11.0.200,
SubnetMask: 255.255.255.0,
Gateway: 10.11.0.1,
InitialConfig: ArubaOsInitialConfig.cfg
}
{Server: 10.101.0.20,
ScopeID: 10.12.0.0,
Name: CiscoVLAN1,
StartRange: 10.12.0.100,
EndRange: 10.12.0.200,
SubnetMask: 255.255.255.0,
Gateway: 10.12.0.1,
InitialConfig: CiscoInitialConfig.cfg
}
{Server: 10.101.0.20,
ScopeID: 10.13.0.0,
Name: JuniperVLAN1,
StartRange: 10.13.0.100,
EndRange: 10.13.0.200,
SubnetMask: 255.255.255.0,
Gateway: 10.13.0.1,
InitialConfig: JuniperInitialConfig.cfg
}
{Server: 10.101.0.20,
ScopeID: 10.14.0.0,
Name: FlexFabricVLAN1,
StartRange: 10.14.0.100,
EndRange: 10.14.0.200,
SubnetMask: 255.255.255.0,
Gateway: 10.14.0.1,
InitialConfig: CW5InitialConfig.cfg
}

The Python Script to Generate the Powershell Script

Nothing too complicated here, I load the variables, pass them into the jinga library and spit out a file with a PS1 extension.


'''Copyright 2015 Christopher Young (@netmanchris)
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the
License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required'''
from jinja2 import Environment, FileSystemLoader, Template
import yaml
import json
ENV = Environment(loader=FileSystemLoader('./'))
with open("dhcp_scopes.yml") as inputfile:
dhcpscopes = yaml.load(inputfile)
template = ENV.get_template("add_dhcp_ps.j2")
#print (template.render(devglobals=devglobals, dev=dev))
with open("./final_dhcp.ps1", "w") as file:
file.write(template.render(dhcpscopes=dhcpscopes))
# Print dictionary generated from yaml
# Render template and print generated config to console
#template = ENV.get_template("Comware5_Template.j2")
#print (template.render(network_global=network_global, device_values = device_values, site= site))

Creating the Reservations

For my specific project, I need to set different DHCP option 67s for some of my clients. Although I could have manually created these as well, I decided that I would just take the same approach and template the whole thing.

The Jinja Template for Creating DHCP Reservations

Very similar to the approach above, I figured out the syntax for one, and then I created a Jinja template using a For loop.


#File to use powershell to automatically build all required DHCP reservations for lab environment
{% for client in reservations %}
Add-DhcpServerv4Reservation -ScopeId {{ client.ScopeId }} -IPAddress {{ client.IPAddress }} -ClientId "{{ client.ClientId }}" -Description "{{ client.Description }}"
Set-DhcpServerv4OptionValue -ComputerName {{ client.DHCPServer }} -ReservedIP {{ client.IPAddress }} -OptionId 67 -Value {{ client.InitialConfigFile }}
{% endfor %}

The CSV file to define the DHCP Reservations

In this case, since I didn’t have to deal with anything more than the reservations, I decided on using a CSV file as the input format. Although YAML is what all the cool kids are doing, using a CSV file allows me to edit this in Excel which I found to be easier for this specific project. There are only a coupe of reservations in here right now, but I’ve got another 30 or so devices which I will need to perform this same step for, so having the ability to quickly add reservations into a CSV file is a good thing in the long run.



IPAddress Name ClientId Description InitialConfigFile DHCPServer ScopeId
10.11.0.10 5400R 64-51-06-79-0c-00 Rserved for 5400R Mobile1st Branch AdpInitialConfig5400R.cfg 10.101.0.20 10.11.0.0
10.11.0.11 2920stack 74-46-a0-ff-78-e3 Reserved for 2920 Stack AdpInitialConfig2920.cfg 10.101.0.20 10.11.0.0

The Python Script to Generate the Powershell Script


'''Copyright 2015 Christopher Young ( @netmanchris )
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the
License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required'''
from jinja2 import Environment, FileSystemLoader, Template
import yaml
import csv
import json
ENV = Environment(loader=FileSystemLoader('./'))
template = ENV.get_template("add_client_reservations_ps.j2")
with open('Reservations.csv') as csvfile:
content = csv.DictReader(csvfile)
reservations = []
for row in content:
reservations.append(row)
with open("./final_reservations.ps1", "w") as file:
file.write(template.render(reservations=reservations))

Wrap up

To be honest, it’s a bit lazy and I wish I had more time to learn more things, but sometimes, you just use what you know to address a problem in a quick and dirty way. Hopefully, someone else will find these useful as well.

At the beginning of the year, I wrote a blog that said my major goal was to be able to automate the configuration of my entire lab with as little effort as possible. Considering how many times I’ve had to manually create DHCP Scopes and Reservations over the years, I think this one will be something that will definitely come in handy. Hopefully someone else will thing so to!

Questions, Comments? Feel free to post below!

@netmanchris

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s