Deep Dive: Access Switch Port Audit Tool¶
"Enterprise Port Intelligence, Distilled to Pure Python."¶
A modular Python utility that connects to Cisco switches (optionally through an SSH jump host), collects comprehensive interface details, PoE information, and neighbor presence, then exports a professional, filters-only Excel workbook with a SUMMARY sheet and one sheet per device. Built for production reliability with YAML-based configuration, intelligent fallback parsing, and customizable credential management.
β¨ What This Tool Does¶
- Audits access ports across multiple Cisco switches in parallel
- Normalises interface names (e.g.,
GigabitEthernet1/0/1βGi1/0/1) for cross-command matching - Enriches interfaces with:
- PoE draw and state (
show power inline) - LLDP/CDP neighbour presence (
show lldp neighbors detail,show cdp neighbors detail) - Classifies port mode & VLAN using
show interfaces status(access / trunk / routed) - Flags stale access ports using conservative rules
- Exports Excel with an at-a-glance SUMMARY and one sheet per device, with filters, frozen header, column auto-size, and conditional formatting
- Shows a progress bar whilst running concurrent device jobs
Design note: The workbook intentionally uses filters only (no Excel tables), and places SUMMARY first.
π― The Nautomation Prime Philosophy in Action¶
Before diving into the code, understand how every design decision reflects our three core principles:
Principle 1: Line-by-Line Transparency¶
Every function in this tool includes explicit documentation of what it does and why it's structured this way. You'll see comments explaining the engineering tradeoffsβwhy we parse with TextFSM and maintain a fallback parser, why we use conservative stale-detection logic, and why conditional formatting in Excel matters for operations teams.
Principle 2: Hardened for Production¶
Access layer audits run on infrastructure that cannot afford downtime. You'll notice patterns like concurrent connection pooling, per-device failure isolation, graceful fallbacks when commands fail, and secure credential rotation. These aren't "nice to have"βthey're mandatory for enterprise reliability.
Principle 3: Vendor-Neutral¶
This tool is built on industry-standard Python libraries: Netmiko (multi-device SSH), Paramiko (jump host tunnelling), Pandas & OpenPyXL (Excel generation), and TextFSM (intelligent parsing). Your skills remain portable across vendors.
π§± Project Layout¶
Note: The
Modules/*.pyfiles encapsulate most environment-specific behaviour. The newconfig_loader.pyprovides a clean interface to YAML-based configuration with validation and type safety.Key Improvement: Configuration is now in human-readable YAML format instead of Python code, making it safer and more accessible to non-developers.
π¦ Requirements¶
- Python: 3.8+
- Python packages:
netmikoparamikopandasopenpyxlpywin32(Windows only; used for Windows Credential Manager integration)
Install with pip:
Optional but Recommended¶
- TextFSM templates (NTC templates) for robust parsing of
show interfaceswhenuse_textfsm=True. - If templates are available and the
NET_TEXTFSMenvironment variable points to them, parsing accuracy improves. - If not available, the script still works and falls back where needed (e.g., it has its own fixed-width parser for
show interfaces status).
βοΈ Configuration System¶
The tool uses a modern YAML-based configuration system introduced in recent updates.
YAML Configuration File (config.yaml)¶
All configurable settings are centralized in config.yaml at the project root. The configuration is loaded via Modules/config_loader.py which provides type-safe property accessors.
Key Configuration Categories:
1. Network Settings:
2. Credential Settings:
3. Performance & Concurrency:
4. Excel Output:
5. Stale Port Detection:
Why YAML Configuration?¶
| Benefit | Explanation |
|---|---|
| Human-Readable | No Python knowledge required to modify settings |
| Version Control Friendly | Plain text format works seamlessly with Git |
| Safer | No code execution risk (pure data) |
| Validated | Config loader validates types and provides defaults |
| Hierarchical | Natural grouping of related settings |
| Documented | Inline comments explain each setting |
Environment Variable Overrides¶
Specific settings can be overridden at runtime via environment variables (primarily for the jump host):
Best Practice: Use
config.yamlfor organizational defaults; use CLI arguments (--direct,--workers, etc.) for per-run overrides.
π Quick Start: Using the Launcher (Recommended)¶
The repository now includes a professional Windows batch launcher (run.bat) that provides the easiest way to run the tool with default settings.
Why Use the Launcher?¶
- Zero configuration required - Just double-click or run from command line
- Automatic validation - Checks for Python environment and required files before execution
- Helpful diagnostics - Clear error messages if something is missing
- Professional interface - Clean output with status indicators and progress messages
- Safe execution - Validates environment before running the script
Using run.bat¶
Option 1: Double-click
Simply double-click run.bat in Windows Explorer to launch the tool with default behavior.
Option 2: Command Line (Default Behavior)
This runs the Access Switch Audit with all default settings from config.yaml.
What the Launcher Does¶
- Validates the environment:
- Checks that the
portable_envvirtual environment exists - Verifies Python executable is present
- Confirms
main.pyexists -
Validates
config.yamlanddevices.txtare present -
Provides clear feedback:
- Shows [OK] for successful checks
- Shows [WARNING] for missing optional files with option to continue
- Shows [ERROR] for critical missing components
-
Displays helpful troubleshooting tips on failure
-
Runs the tool:
- Activates the virtual environment
- Executes the main script
- Captures and displays the exit code
- Provides common troubleshooting tips if errors occur
Example Output¶
π Advanced: Command Line with Arguments¶
For advanced users who need to customize behavior beyond the defaults, you can still run the tool directly with Python and command-line arguments.
When to Use Command Line Arguments¶
Use python main.py with arguments when you need to:
- Override default settings from
config.yaml - Specify a different devices file
- Change output filename
- Adjust worker thread count
- Enable debug mode
- Force direct connections (bypass jump host)
Method 1: Using the Launcher with Arguments¶
You can pass arguments to run.bat and they will be forwarded to the Python script:
Method 2: Direct Python Execution¶
Activate the virtual environment and run Python directly:
Available Command-Line Arguments¶
| Argument | Description | Default |
|---|---|---|
--devices, -d |
Path to devices file | devices.txt |
--output, -o |
Output Excel filename | audit.xlsx |
--workers, -w |
Number of concurrent threads | 10 |
--stale-days |
Days threshold for stale ports | 30 |
--direct |
Skip jump host, connect directly | False |
--debug |
Enable debug-level logging | False |
Example: Custom audit with direct connections:
ποΈ Technical Architecture¶
The tool operates as a modular Python application with six primary components:
| Component | Responsibility | Why It Matters |
|---|---|---|
| Config Loader | YAML parsing and validation with type-safe properties | Settings are centralized, validated, and safe from code injection |
| CredentialManager | Secure credential retrieval from OS stores | Passwords never touch plaintext or config files |
| JumpManager | Persistent SSH tunnelling through a bastion host | Centralises network access control; supports air-gapped environments |
| PortAuditor (Netmiko) | Parallel device SSH connections and command collection | Audits 20+ switches in minutes, not hours |
| PortIntelligence | Multi-source port classification and risk flagging | Detects stale, misconfigured, or problematic ports automatically |
| ExcelReporter | Professional, templated workbook generation | Operations teams get insights immediately, not raw data dumps |
Key Design Patterns¶
1. Modular Configuration: - Settings separated from code (YAML vs Python) - Config loader provides validation and type safety - Environment-specific overrides supported
2. Intelligent Fallback Parsing: - Primary: TextFSM templates (when available) - Fallback: Custom fixed-width parsers - Ensures reliability even without external dependencies
3. Multi-Threaded Execution: - ThreadPoolExecutor with configurable worker count - Thread-safe data accumulation with locks - Per-device failure isolation
4. Graceful Error Handling: - Exponential backoff retry logic - Per-device error capture (doesn't stop entire audit) - Comprehensive logging for troubleshooting
π Intelligent Parsing: The Heart of the Tool¶
Why Intelligent Parsing Matters¶
The Problem: Cisco CLI output varies by device model, IOS version, and platform. show interfaces status might be formatted differently on a Catalyst 2960 vs a 9300. TextFSM templates might not exist for your specific platform.
The Solution: Multi-tier parsing strategy with intelligent fallbacks.
Parsing Strategy: TextFSM + Custom Fallback¶
Why This Approach: - TextFSM provides structured parsing when templates exist - Returns empty list (not exception) if parsing fails - Main logic continues with custom parsers
Custom Fixed-Width Parser: parse_show_interfaces_status()¶
This is the authoritative source for port mode and VLAN classification.
Step 1: Identify Header Row
Why: Header detection must be flexible. Different IOS versions capitalize differently.
Step 2: Extract Column Positions
Why This Matters: - Fixed-width parsing is more reliable than regex for tabular CLI output - Dynamically calculated positions adapt to slight formatting variations - Slice objects provide clean substring extraction
Step 3: Parse Data Rows
Why Status Normalization: - Different IOS versions use slight variations ("connected" vs "connect") - Normalized values enable reliable conditional formatting in Excel - Consistent categorization across device types
Port Mode Classification¶
Why This Logic: - VLAN column is the most reliable indicator of port mode - Trunk ports show "trunk" or "rspan" in VLAN field - Routed ports show "routed" - Everything else is access (may show VLAN number)
π PoE Enrichment: Multi-Source Data Fusion¶
The PoE Challenge¶
Problem: PoE data (show power inline) uses different interface naming than show interfaces status. Example:
- Status command: Gi1/0/1
- PoE command: GigabitEthernet1/0/1
Solution: Interface name aliasing and multi-key lookups.
Interface Name Normalization¶
Why This Matters: - Enables reliable cross-command matching - Handles all common Cisco interface types - Works across different IOS versions and platforms
Alias-Based Lookup¶
Why Multiple Aliases: - Different commands use different naming conventions - Maximizes successful PoE data correlation - Prevents data loss due to naming mismatches
π¨ Stale Port Detection: Conservative Risk Assessment¶
The Business Problem¶
Scenario: You have 1,000 switch ports. How do you identify which ones are truly unused vs. temporarily disconnected vs. connected to equipment that's powered off?
False Positives Are Expensive: - Marking an active port as "stale" disrupts operations - Users lose network access - Help desk tickets spike
False Negatives Waste Resources: - Unused ports consume switch capacity - Security risk (unauthorized devices can plug in)
Conservative Detection Strategy¶
Rule 1: Only Classify Access Ports
Why: Trunk and routed ports connect switches to each other. They should never be flagged as stale.
Rule 2: Connected Ports β Check Activity
Why: - Port is physically connected - But hasn't passed traffic in N days - Likely a powered-off device or misconfigured endpoint
Rule 3: Disconnected Ports β Check for Indicators
Why This Conservative Approach:
| Indicator | Interpretation |
|---|---|
| PoE draw > 0 | Device is powered (IP phone, camera, AP) |
| LLDP/CDP neighbor | Device is network-aware (switch, phone, AP) |
| Both absent | Likely unused cable or dead device |
Example Scenarios:
| Status | PoE | Neighbor | Last Input | Classification | Reasoning |
|---|---|---|---|---|---|
| connected | 7.0W | Yes | 10 days | active | IP phone actively drawing power |
| connected | 0W | No | 45 days | stale | Connected but no traffic for 45+ days |
| notconnect | 0W | No | N/A | stale | Disconnected, no indicators of use |
| notconnect | 6.5W | No | N/A | available | PoE device present (might be powered off) |
| notconnect | 0W | Yes | N/A | available | Neighbor detected (might be rebooting) |
Time Parsing: Handling Cisco Duration Formats¶
Why Multiple Format Support: - Different IOS versions use different time formats - Ensures accurate stale detection across all platforms
π Credentials & Security¶
The script retrieves device credentials using Modules/credentials.py:
- Primary: Windows Credential Manager (target name from
config.yaml: defaultMyApp/ADM) - Fallback: Interactive prompt for username and password (secure, not echoed)
- Enable secret: Retrieved by
get_enable_secret()if configured inconfig.yaml, otherwise not required
Note: If you are running on Linux/macOS, ensure
credentials.pyprompts for credentials or implements your preferred secure store. On Windows,pywin32enables Credential Manager access.Important: Never hard-code credentials in the repository. Use the secure store or environment prompts.
Configuration: Credential Manager targets are set in
config.yamlunder thecredentialssection:
π°οΈ Jump Host (Bastion) Behaviour¶
main.pyreadsjump_hostfrom thenetworksection ofconfig.yaml- Default: the script uses the jump host if
--directis not supplied --directwill skip the jump host entirely and attempt direct SSH connections
Example configuration in config.yaml:
The JumpManager maintains a persistent SSH session to the bastion and proxies device connections through it.
How JumpManager Works:
Why direct-tcpip Channel: - No port forwarding needed on bastion - All traffic stays within authenticated SSH session - Cleaner than local port forwarding - Works with restrictive bastion configurations
οΏ½οΈ Device List File¶
Provide a plain-text file with one device per line. Lines that are blank or start with # are ignored.
Note: Hostnames must be resolvable from the machine (or via the jump host, depending on your SSH setup).
π Quick Start¶
- Install dependencies (see Requirements).
- Create
devices.txtwith your targets (see Device list file). - (Optional) Configure
Modules/config.pywith yourJUMP_HOST. - Run the audit:
π§ CLI Reference¶
main.py exposes the following command-line options:
Required vs Optional¶
- Required:
--devices - Optional: everything else
οΏ½π PortAuditor: The Threaded Collection Engine¶
Why Parallel Port Auditing is Essential¶
The Problem: Auditing 50 switches serially with 5 commands per device = 250 SSH round-trips. At 2 seconds per connection, that's 8+ minutes of waiting.
The Solution: Thread pool with 10 concurrent workers = 10 simultaneous SSH sessions. Same 50 switches audited in 1-2 minutes.
Thread-Safe Architecture¶
Why Thread Locks Matter: - Without locks, multiple threads writing to the same list causes data corruption - The lock ensures atomic append operations - Minimal lock contention because we hold locks for microseconds, not seconds
Command Collection Strategy¶
For each device, the tool collects five commands in sequence:
| Command | Purpose | Fallback |
|---|---|---|
show version |
Extract hostname, OS version, uptime | Use management IP if hostname parse fails |
show interfaces |
Parse interface types, error counters, activity | Use TextFSM if available; use internal parser otherwise |
show interfaces status |
Extract port mode, VLAN, status using fixed-width parsing | Built-in fallback parser (no external dependency) |
show power inline |
Collect PoE admin/oper state and power draw | Empty dict if device is non-PoE or command fails |
show cdp/lldp neighbors detail |
Detect peer devices on each port | Boolean flag (true if neighbour present) |
Why This Command Set? - Comprehensive but minimal: each command provides data no other command offers - Covers the three dimensions of port health: configuration (mode/VLAN), activity (errors, last input), attachment (PoE, neighbours)
The Intelligence Layer: Port Classification¶
Classification Logic:
From show interfaces status, inspect the VLAN column:
- If
trunkorrspanβ Trunk - If
routedβ Routed - Otherwise β Access
Why This Matters: - Different port types require different stale-detection rules - Access ports should be connected to hosts; trunk ports connect infrastructure - This classification enables intelligent filtering and reporting
π Stale Logic β How Ports Are Flagged¶
A conservative approach is used only for ports in access mode and when --stale-days > 0:
- If Status =
connectedβ mark stale = True only ifLast input β₯ <stale-days> - If Status β
connectedβ mark stale = True when both conditions hold: - No PoE draw (PoE power is blank/
-/0.0), and - No LLDP/CDP neighbour present on the port
This tends to avoid false positives on trunk/routed ports and on access ports actively in use.
Note: You can disable stale flagging entirely by setting
--stale-days 0.
π§ͺ What the Script Collects¶
For each device the script attempts to gather:
- Hostname (from
show running-config | include ^hostnameor CLI prompt fallback) - Interfaces via TextFSM (
show interfaces) when available - Port mode & VLAN via a robust, fixed-width parser of
show interfaces status - PoE details: admin/oper state, power draw (W), class, device (
show power inline) - Neighbour presence: LLDP/CDP seen on the port (boolean)
- Error counters: input, output, CRC (from
show interfacesparsed data) - Activity indicator: "Last input" time (seconds parsed when present)
π€ Excel Output Structure¶
The workbook contains:
1) SUMMARY Sheet (First)¶
- One row per device, with a final TOTAL row (sums numeric columns)
- Columns include:
Device,Mgmt IP,Total Ports (phy)Access Ports,Trunk Ports,Routed PortsConnected,Not Connected,Admin Down,Err-Disabled% Access of Total,% Trunk of Total,% Routed of Total,% Connected of Total
2) One Sheet Per Device¶
Columns typically include (when available):
Device,Mgmt IP,Interface(long form),DescriptionStatus(normalised: connected / notconnect / administratively down / err-disabled)AdminDown,Connected,ErrDisabled(booleans for quick filters)Mode(access/trunk/routed),VLANDuplex,Speed,TypeInput Errors,Output Errors,CRC ErrorsLast Input(raw text)PoE Power (W),PoE Oper,PoE AdminLLDP/CDP Neighbour(boolean)Stale (β₯<N> d)(boolean)
Formatting¶
- Frozen header (
A2) and AutoFilter across all columns - Auto-sized columns with sensible min/max widths
- Conditional formatting:
Status = connectedβ greenStatus = notconnect/err-disabledβ redStatus = administratively downβ grey- Any
*Errors> 0 β red PoE Power (W)> 0 β greenStale (β₯N d)= TRUE β red
βοΈ Performance & Concurrency¶
- Uses a
ThreadPoolExecutorwith--workersthreads (default 10) - An event-driven progress bar updates as jobs start/finish
- Each device is independent; a failure on one does not stop others
The script prints an event-driven progress bar like:
On completion, the Excel workbook is written to the filename you specify (default audit.xlsx).
οΏ½ Logging, Debug, and Errors¶
- Add
--debugto surface additional prints (e.g., enable mode attempts, jump host info, file counts) - Per-device errors are captured into the device's summary row (and a minimal sheet may be created with the error text so the workbook always reflects all devices)
Common runtime issues & tips:
- Authentication failures β check Credential Manager entry or typed credentials
- SSH connectivity β verify reachability from the workstation or via the jump host
- TextFSM templates missing β parsing still proceeds, but some fields may be blank
- Channel/line rate limits on older devices β consider lowering
--workers
π§ Extending and Customizing¶
- Credentials: Adapt
Modules/credentials.pyto your environment (Linux keyring, Azure Key Vault, etc.) - Jump host: Tune
Modules/jump_manager.py(keep-alive, ciphers, auth methods) as needed - Connection behaviour: Modify
Modules/netmiko_utils.pyfor device types, timeouts, or SSH options - Configuration: Edit
config.yamlto set organizational defaults: network.jump_host: Default bastion serverconcurrency.default_workers: Concurrent device sessionsstale_detection.default_stale_days: Stale port thresholdcredentials.cred_target: Credential Manager target nameoutput.default_filename: Default Excel output filename- Output columns: Adjust record construction in
main.py(search fordetailed.append({...})) - Conditional formatting: Tweak
_format_worksheet()inmain.py - Parsers: Modify
parse_show_interfaces_status()orparse_show_power_inline()for custom parsing logic
Example config.yaml for Enterprise:
π Security Considerations¶
- Prefer secure stores over plaintext
- Limit who can run the tool and who can read the generated Excel
- When using a jump host, ensure strong authentication and proper network segmentation
π§© Compatibility¶
- Target devices: Cisco IOS/IOS-XE access and distribution switches reachable via SSH
- The tool relies on Netmiko; specify the right device type(s) inside
netmiko_utils.py - TextFSM/NTC templates significantly improve interface parsing fidelity but are not strictly required
Tested Devices¶
This tool has been tested and verified on the following Cisco IOS and IOS-XE platforms:
- Catalyst 9200 Series
- Catalyst 3650 Series
- Catalyst 3650C
- Catalyst 3650CG
- Catalyst 3650CX
- Catalyst 2960X Series
- Catalyst 2960 Series
Note: The tool should work with any Cisco IOS/IOS-XE device that supports the required show commands (interfaces, status, power inline, CDP/LLDP). The devices listed above have been explicitly tested and validated.
β Examples¶
π§ FAQs¶
Q: Do I need NTC TextFSM templates?
A: They are recommended for better show interfaces parsing. Without them, the script still works and uses its internal parser for show interfaces status and best-effort logic elsewhere.
Q: Where do credentials come from?
A: On Windows, from Credential Manager (default target MyApp/ADM). Otherwise, you are prompted interactively or you can adapt credentials.py to your secret store.
Q: How is Mode determined?
A: From show interfaces status: if VLAN column is trunk/rspan β trunk; if routed β routed; otherwise access.
Q: How is a port considered stale?
A: Only for access ports and when --stale-days > 0. Connected ports are flagged stale only if Last input β₯ N days. Disconnected ports require both no PoE draw and no LLDP/CDP neighbour to be flagged stale.
π Learning Outcomes¶
After studying this code, you should understand:
β
YAML Configuration Management β How to separate configuration from code using YAML with Python
β
Fixed-Width Parsing β Reliable CLI output parsing without external dependencies
β
Multi-Source Data Fusion β Correlating data across different commands using interface name aliasing
β
Conservative Risk Assessment β Stale port detection logic that minimizes false positives
β
Thread-Safe Concurrency β Parallel device audits with proper lock management
β
Intelligent Fallback Strategy β TextFSM + custom parsers for maximum compatibility
β
SSH Tunneling β Jump host integration with Paramiko direct-tcpip channels
β
Excel Automation β Professional workbook generation with conditional formatting
β
Exponential Backoff β Retry logic for transient network failures
β
Credential Management β Secure OS-level credential storage integration
Key Code Patterns Demonstrated¶
Pattern 1: Graceful Degradation
Pattern 2: Multi-Key Lookup
Pattern 3: Thread-Safe Accumulation
Pattern 4: Conservative Classification
Pattern 5: Type-Safe Configuration
π Distribution & Execution¶
Consistent with the Nautomation Prime delivery model, this tool is available in multiple formats:
-
Zero-Install Portable Bundle: A self-contained package including the Python interpreter and all libraries (Netmiko, Pandas, OpenPyXL) for use on restricted Windows jump boxes.
-
Scheduled Docker Appliance: A pre-built container designed for autonomous execution and periodic port auditing.
-
Source Code: Full access to customize parsing logic, add vendor-specific commands, or integrate with your CMDB.
π Licence¶
GNU General Public Licence v3.0
π€ Author¶
Christopher Davies
Mission: To empower network engineers with transparent, hardened Python tools that eliminate manual audits and expose infrastructure health at a glance.