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 ).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'''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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'''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!