Network Inventory Script — Timex
This writeup documents the development of a custom Python-based network inventory script built for Timex. The tool was created to automate the discovery and documentation of all devices on the Timex internal network — replacing a manually maintained spreadsheet that was frequently outdated and inaccurate. It scans the network, identifies hosts, resolves hostnames, fingerprints operating systems, and exports a structured inventory report as HTML and CSV for the IT team.
Project Info
- Client: Timex
- Type: Internal Network Inventory Automation Tool
- Language: Python 3
- Dependencies: python-nmap, scapy, Jinja2, csv, socket
Step 1 — Requirements Gathering
The Timex IT team outlined the following requirements before development began:
- Automatically discover all live hosts on the internal subnet
- Resolve hostnames and MAC addresses for each device
- Identify the operating system and open services per host
- Flag devices that are new or unrecognised against a known baseline
- Export results as CSV for asset management and HTML for management review
- Run on a scheduled basis via cron without manual intervention
A baseline CSV of known devices was provided by the client at
project start — new devices discovered during scans are automatically
flagged as UNKNOWN in the output report.
Step 2 — Project Structure
The tool was structured as a single scheduled script with modular functions for discovery, fingerprinting, and reporting:
/timex-inventory
inventory.py -- main entry point and scheduler
discovery.py -- ARP sweep and host discovery
fingerprint.py -- OS and service fingerprinting
reporter.py -- HTML and CSV report generation
baseline.csv -- known device registry
templates/
inventory.html -- Jinja2 HTML report template
logs/ -- scan logs per run
requirements.txt
Scan logs are written per run with a timestamp filename — allowing the IT team to compare results across runs and track when new devices first appeared on the network.
Step 3 — Host Discovery (ARP Sweep)
ARP is used for host discovery on the local subnet — faster and more reliable than ICMP ping which is frequently blocked by Windows firewall rules:
# discovery.py
from scapy.all import ARP, Ether, srp
import socket, datetime
def arp_sweep(subnet):
print(f"[*] Scanning subnet: {subnet}")
arp = ARP(pdst=subnet)
ether = Ether(dst="ff:ff:ff:ff:ff:ff")
packet = ether / arp
result = srp(packet, timeout=3,
verbose=False)[0]
hosts = []
for sent, received in result:
try:
hostname = socket.gethostbyaddr(
received.psrc)[0]
except socket.herror:
hostname = "unknown"
hosts.append({
"ip": received.psrc,
"mac": received.hwsrc,
"hostname": hostname,
"discovered": datetime.datetime.now()
.strftime("%Y-%m-%d %H:%M")
})
print(f"[*] {len(hosts)} hosts discovered")
return hosts
MAC addresses are captured alongside IPs — allowing the tool to track devices even if they change IP via DHCP between scans.
Step 4 — OS and Service Fingerprinting
Each discovered host is passed to Nmap for OS detection and top-port service enumeration:
# fingerprint.py
import nmap
def fingerprint_host(ip):
nm = nmap.PortScanner()
nm.scan(ip, arguments="-O --top-ports 20 -T4")
result = {
"os": "unknown",
"services": []
}
if ip in nm.all_hosts():
host = nm[ip]
# OS detection
if "osmatch" in host and host["osmatch"]:
result["os"] = \
host["osmatch"][0]["name"]
# Open services
for proto in host.all_protocols():
for port in host[proto].keys():
svc = host[proto][port]
if svc["state"] == "open":
result["services"].append(
f"{port}/{proto} "
f"({svc['name']})"
)
return result
OS detection requires root or administrator privileges. The script checks for elevated permissions on startup and exits with a clear error message if not satisfied.
Step 5 — Baseline Comparison
Each discovered host is checked against the known device
baseline CSV. Unrecognised MAC addresses are flagged
as UNKNOWN in the report:
# inventory.py
import csv
def load_baseline(filepath="baseline.csv"):
baseline = {}
with open(filepath, newline="") as f:
reader = csv.DictReader(f)
for row in reader:
baseline[row["mac"].lower()] = {
"label": row["label"],
"owner": row["owner"],
"type": row["type"]
}
return baseline
def check_baseline(host, baseline):
mac = host["mac"].lower()
if mac in baseline:
host.update(baseline[mac])
host["status"] = "KNOWN"
else:
host["label"] = "Unrecognised Device"
host["owner"] = "—"
host["type"] = "—"
host["status"] = "UNKNOWN"
return host
Unknown devices trigger a warning entry in the scan log and are highlighted in red in the HTML report — giving the IT team immediate visibility of rogue or new devices.
Step 6 — HTML and CSV Report Generation
Scan results are exported as a timestamped HTML report for management review and a CSV for the asset register:
# reporter.py
from jinja2 import Environment, FileSystemLoader
import csv, datetime, os
def generate_report(hosts, output_dir="reports"):
os.makedirs(output_dir, exist_ok=True)
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M")
# HTML report
env = Environment(
loader=FileSystemLoader("templates"))
tmpl = env.get_template("inventory.html")
html = tmpl.render(
hosts=hosts,
generated=ts,
total=len(hosts),
unknown=sum(
1 for h in hosts
if h["status"] == "UNKNOWN")
)
with open(f"{output_dir}/inventory_{ts}.html","w") as f:
f.write(html)
# CSV export
with open(f"{output_dir}/inventory_{ts}.csv",
"w", newline="") as f:
w = csv.writer(f)
w.writerow(["IP","MAC","Hostname","OS",
"Services","Label","Owner",
"Type","Status","Discovered"])
for h in hosts:
w.writerow([
h["ip"], h["mac"], h["hostname"],
h["os"],
" | ".join(h["services"]),
h["label"], h["owner"],
h["type"], h["status"],
h["discovered"]
])
The HTML report includes a summary banner showing total hosts found, known devices, and unknown device count — colour coded green for known and red for unknown.
Step 7 — Scheduled Execution (Cron)
The script was configured to run automatically every night at 02:00 via cron on the Timex IT server:
# Add to crontab — run as root for OS detection
sudo crontab -e
# Run nightly at 02:00
0 2 * * * /usr/bin/python3 \
/opt/timex-inventory/inventory.py \
--subnet 192.168.1.0/24 \
>> /opt/timex-inventory/logs/cron.log 2>&1
# Verify the job is registered
sudo crontab -l
Each nightly run produces a new timestamped report in the
reports/ directory. The IT team configured a
shared network drive mount so reports are accessible from
any workstation on the Timex network without logging into
the scan server.
Step 8 — Installation and Usage
The tool installs into any Python 3 environment with Nmap present on the system:
# Clone or copy the project to the server
cd /opt/timex-inventory
# Install Python dependencies
pip3 install -r requirements.txt
# requirements.txt
python-nmap==0.7.1
scapy==2.5.0
Jinja2==3.1.2
# Ensure Nmap is installed
nmap --version
# Run a manual scan
sudo python3 inventory.py \
--subnet 192.168.1.0/24
# Run against multiple subnets
sudo python3 inventory.py \
--subnet 192.168.1.0/24 192.168.2.0/24
# Output: reports/inventory_20240115_0200.html
# reports/inventory_20240115_0200.csv
Key Takeaways
- ARP sweeps are faster and more reliable than ICMP ping for local subnet discovery
- Tracking MAC addresses alongside IPs identifies devices even when DHCP reassigns their IP
- Baseline comparison immediately surfaces rogue or unrecognised devices on the network
- Timestamped reports per run allow the IT team to track when new devices first appeared
- Cron scheduling removes the human dependency — inventory stays current without manual effort
- Exporting both HTML and CSV covers management reporting and asset register updates simultaneously
Tools Used
Python 3— core language for the inventory toolScapy— ARP sweep for host and MAC address discoverypython-nmap— OS fingerprinting and service enumerationJinja2— HTML report templatingcsv / socket— baseline comparison and hostname resolutioncron— scheduled nightly executionVS Code— development environment